Search code examples
springspring-boothikaricp

Spring: Allow missing database connection


I have a project connecting to multiple databases (Oracle & SQLServer), some of which are not always mandatory nor available when starting the application - especially on a dev environment.

I'm looking for a way to allow the application to start without a database connection right-on. We only require a connection to these DB when doing specific tasks, which use a Staging Database requiring a specific network access not always available on dev.

Here, with our Oracle connection (which is the one being under a specific network) :

We had a configuration that allowed the application to start, but was throwing errors when running the Tests because of the erroneous HikariDataSource cast :

@Configuration
@EnableJpaRepositories(
        basePackages = "com.bar.foo.repository.oracle",
        entityManagerFactoryRef = "oracleEntityManager",
        transactionManagerRef = "oracleTransactionManager"
)
public class OracleConfiguration {
private final Logger            log = LoggerFactory.getLogger(this.getClass());

@Inject
private Environment             environment;

@Value("${spring.datasource.oracle.ddl-auto:none}")
private String hibernateddlAuto;

@Value("${spring.datasource.oracle.dialect:org.hibernate.dialect.Oracle10gDialect}")
private String hibernateDialect;

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

@Bean
@Primary
@ConfigurationProperties("spring.datasource.hikari")
public DataSource oracleDataSource() {
    HikariDataSource ds = (HikariDataSource) oracleDataSourceProperties().initializeDataSourceBuilder().build();
    ds.setPoolName(environment.getProperty("spring.datasource.oracle.poolName"));
    return ds;
}


@Bean
@Primary
public LocalContainerEntityManagerFactoryBean oracleEntityManager() {
    LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
    em.setPackagesToScan("com.bar.foo.domain.oracle");
    HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
    em.setJpaVendorAdapter(vendorAdapter);
    HashMap<String, Object> properties = new HashMap<>();
    properties.put("hibernate.ddl-auto", hibernateddlAuto);
    properties.put("hibernate.dialect", hibernateDialect);
    em.setJpaPropertyMap(properties);
    em.setDataSource(oracleDataSource());
    return em;
}

@Primary
@Bean
public PlatformTransactionManager oracleTransactionManager() {

    JpaTransactionManager transactionManager


     = new JpaTransactionManager();
    transactionManager.setEntityManagerFactory(
            oracleEntityManager().getObject());
    return transactionManager;
}
}

I moved this configuration to extend HikariConfig as following :

@Configuration
@EnableJpaRepositories(
    basePackages = "com.bar.foo.repository.oracle",
    entityManagerFactoryRef = "oracleEntityManager",
    transactionManagerRef = "oracleTransactionManager"
)
@ConfigurationProperties("spring.datasource.oracle")
public class OracleConfiguration extends HikariConfig{


    @Bean
    @Primary
    public DataSource oracleDataSource() {
      return new HikariDataSource(this);
    }

    @Bean
    @Primary
public LocalContainerEntityManagerFactoryBean oracleEntityManager() {
  LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
  em.setPackagesToScan("com.bar.foo.domain.oracle");
  HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
  em.setJpaVendorAdapter(vendorAdapter);
  HashMap<String, Object> properties = new HashMap<>();
      properties.put("hibernate.ddl-auto", hibernateddlAuto);
      properties.put("hibernate.dialect", hibernateDialect);
  em.setJpaPropertyMap(properties);
  em.setDataSource(oracleDataSource());
  return em;
}

@Bean
@Primary
public PlatformTransactionManager oracleTransactionManager() {

  JpaTransactionManager transactionManager
    = new JpaTransactionManager();
  transactionManager.setEntityManagerFactory(
    oracleEntityManager().getObject());
  return transactionManager;
}

Which does not start if the Oracle DB is unavailable.
Using an embedded DB would not suit our use case, as we do not always need this connection, and when we do we need specific data replicated from Production. Splitting this on multiple microservice/applications is also a no-go, as it would be a huge refacto, and does not really fit our use case (we aggregate data from multiple sources into a final one).

Is there a simple way to allow this ?


Solution

  • HikariCP provides some really nice configuration properties which may suit your needs. Specifically (the first one on that list) initializationFailTimeout:

    This property controls whether the pool will "fail fast" if the pool cannot be seeded with an initial connection successfully...

    A value less than zero will bypass any initial connection attempt, and the pool will start immediately while trying to obtain connections in the background. Consequently, later efforts to obtain a connection may fail.

    If you want to solve your problem this way, i.e. by hiding any initialization failures (by setting a negative initializationFailTimeout value), then you'll just have to make sure you have the right logic in case the DB is unaccesible/down when your getting a connection from the pool.