We are using multi tenant hibernate + Spring boot (2.2.0) as described in Multi-Tenancy in Hibernate. We are providing MultiTenantConnectionProvider and CurrentTenantIdentifierResolver when creating the entity manager (Refer to sample below).
In our MultiTenantConnectionProvider, The target database for a tenant is not statically configured. Instead, It dynamically determines the target database details of current tenant by calling another service and then creates datasource for it.
Now , we are facing an issue that when spring boot server is started, Hibernate is making a call to MultiTenantConenctionProvider.getAnyConnection() method. At the time of server startup, there is no tenant in the context and hence we do not have any database to create a connection for. Also, we do not have any dummy database in our setup which we can use during server startup time
- If we return null, the server startup fails with a NullPointerException
- If we override anyConnection() method and throw an exception (as can be see in the code below), the server just logs the exception and continues with the server startup.
With 2, Things work fine and we are able to make progress. However, we really want to get rid of this unnecessary exception being logged everytime the spring boot server is restarted.
We are looking for a way to turn off hibernate behaviour of trying to connect to a db during server startup especially when hibernate’s multi-tenant strategy is set. This behavior is inconsistent for a multi-tenant software
Is there any way, we can prevent hiberate from trying get a connection (i.e making a call to MultiTenantConenctionProvider.getAnyConnection()) during server startup ?
Please advise
Following is a sample of our Spring configuration
@Configuration
@EnableTransactionManagement
public class MultiTenantHibernateJpaConfig {
@Autowired
private JpaProperties jpaProperties;
@Bean
public MultiTenantConnectionProvider multiTenantConnectionProvider() {
return new MultiTenantConnectionProviderImpl();
}
@Bean
public CurrentTenantIdentifierResolver currentTenantIdentifierResolver() {
return new CurrentTenantIdentifierResolverImpl();
}
@Value("${jpaconfig.packages-to-scan:#{null}}")
private String packagesToScan;
@Bean
@Primary
public LocalContainerEntityManagerFactoryBean entityManagerFactory(MultiTenantConnectionProvider multiTenantConnectionProvider,
CurrentTenantIdentifierResolver currentTenantIdentifierResolver) {
Map<String, Object> hibernateProps = new LinkedHashMap<>();
hibernateProps.put(Environment.MULTI_TENANT, MultiTenancyStrategy.SCHEMA);
hibernateProps.put(Environment.MULTI_TENANT_CONNECTION_PROVIDER, multiTenantConnectionProvider);
hibernateProps.put(Environment.MULTI_TENANT_IDENTIFIER_RESOLVER, currentTenantIdentifierResolver);
hibernateProps.put("javax.persistence.schema-generation.database.action", "none");
hibernateProps.put("javax.persistence.query.timeout", "5");
hibernateProps.put(Environment.DIALECT, "org.hibernate.dialect.Oracle10gDialect");
hibernateProps.putAll(this.jpaProperties.getProperties());
LocalContainerEntityManagerFactoryBean result = new LocalContainerEntityManagerFactoryBean();
if(packagesToScan != null) {
//handle multiple packages separated by comma
result.setPackagesToScan(packagesToScan.split("\\s*,\\s*"));
}
result.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
result.setJpaPropertyMap(hibernateProps);
return result;
}
Following is the MultiTenantConnectionProvider implementation
public class MultiTenantConnectionProviderImpl extends AbstractDataSourceBasedMultiTenantConnectionProviderImpl {
private static final long serialVersionUID = 1L;
public MultiTenantConnectionProviderImpl()
{
}
@Override
protected DataSource selectAnyDataSource()
{
return null;
}
@Override
protected DataSource selectDataSource(String tenantIdentifier) {
//This in turn makes a call to a remote service to fetch current tenant
//database details and then creates a datasource for it.
return DataSourceManager.getDataSource(tenantIdentifier);
}
@Override
public Connection getAnyConnection() throws SQLException{
//Called during server startup. Cannot do any thing as tenant is not known
// and there is no default datasource
throw new SQLException("not implemented in multi-tenant environment");
}
@Override
public void releaseAnyConnection(Connection connection) throws SQLException
{
//Called during server startup. Cannot do any thing as tenant is not known
// and there is no default datasource
throw new SQLException("not implemented in multi-tenant environment");
}
}