2

I'm using RoutingDataSource to dynamically create datasources for each tenant of our application. After 8 - 12 hours application application losts connection with database and I'm getting jpa transaction exception. I found that the following properties are responsible for validation and sustaining database connection so I placed them in my application.properties.

spring.datasource.initialize=false
spring.datasource.test-while-idle=true
spring.datasource.test-on-borrow=true
spring.datasource.validation-query=SELECT 1

Data source bean is created in the following class. How to inject above properties to each target datasource?

...

@Configuration
public class RoutingDataSourceConfiguration {

    public static final String DEFAULT_TENANT_NAME = "default_tenant";

    @Autowired
    private RoutingDataSourceProperties routingProperties;

    /**
     * Defines the data source for the application
     *
     * @return
     */
    @Bean
    @ConfigurationProperties(prefix = "spring.datasource")
    public DataSource dataSource() {
        Map<Object, Object> dataSources = new HashMap<>();
        for (Map.Entry<String, DataSourceProperties> entry : routingProperties.getDataSources().entrySet()) {
            DataSourceProperties dataSourceProperties = entry.getValue();
            dataSources.put(entry.getKey(), createDataSource(dataSourceProperties));
        }
        RoutingDataSource dataSource = new RoutingDataSource();
        dataSource.setLenientFallback(false);
        dataSource.setDefaultTargetDataSource(createDefaultDataSource());
        dataSource.setTargetDataSources(dataSources);
        dataSource.afterPropertiesSet();
        return dataSource;
    }

    private DataSource createDataSource(DataSourceProperties dataSourceProperties) {
        DataSourceBuilder dataSourceBuilder = new DataSourceBuilder(this.getClass().getClassLoader());
        dataSourceBuilder.driverClassName(dataSourceProperties.getDriverClassName())
                .url(dataSourceProperties.getUrl())
                .username(dataSourceProperties.getUsername())
                .password(dataSourceProperties.getPassword());

        if (dataSourceProperties.getType() != null) {
            dataSourceBuilder.type(dataSourceProperties.getType());
        }
        return dataSourceBuilder.build();
    }

    private DataSource createDefaultDataSource() {
        Map<String, DataSourceProperties> dataSources = routingProperties.getDataSources();
        if (!dataSources.containsKey(DEFAULT_TENANT_NAME)) {
            throw new BeanCreationException(String.format(
                    "No configuration for default tenant '%s' found", DEFAULT_TENANT_NAME));
        }
        DataSourceProperties dataSourceProperties = dataSources.get(DEFAULT_TENANT_NAME);
        return createDataSource(dataSourceProperties);
    }
}
b0ro
  • 23
  • 1
  • 5

1 Answers1

2

I needed to set validation-query query manually when creating a datasource pragmatically as per 77.2 (and in my case multiple datasources). I know Springboot 1.4+ has changed the properties set, but this is not the issue you are having.

It's a bit ugly, but it worked for me. It assumes you are using Tomcat JDBC pooling (default when using spring-boot-starter-web):

@Value("${spring.datasource.validation-query}")
private String validationQuery;

@Bean
@ConfigurationProperties("spring.datasource")
@Primary
  public DataSourceProperties ftmDataSourceProperties() {
  return new DataSourceProperties();
}

@Bean
@ConfigurationProperties("spring.datasource")
@Primary
public DataSource ftmDataSource() {
  DataSource ds = ftmDataSourceProperties().initializeDataSourceBuilder().build();
  setTypeSpecificProperties(validationQuery,ds);
  return ds;
}

private void setTypeSpecificProperties(String validationQuery, DataSource dataSource) {
  org.apache.tomcat.jdbc.pool.DataSource typedDS = (org.apache.tomcat.jdbc.pool.DataSource) dataSource;
  typedDS.setValidationQuery(validationQuery);
  typedDS.setTestOnBorrow(true);
  typedDS.setLogValidationErrors(true);
}
rjdkolb
  • 10,377
  • 11
  • 69
  • 89
  • 2
    This helped me figure out a problem we were dealing with for a while. Strangely, Spring Boot wouldn't pick up the SQL connection from the pool by default and every time DB connection gets dropped, our app running on PCF would stop connecting to SQL DB. Our DataSource object was javax.sql.datasource and calling setTypeSpecificProperties method did the trick. – Stephen C Dec 01 '18 at 05:39