在 Hibernate 多租户配置中禁用 Spring 数据源配置
Posted
技术标签:
【中文标题】在 Hibernate 多租户配置中禁用 Spring 数据源配置【英文标题】:Disable Spring datasource configuration in Hibernate multi tenant configuration 【发布时间】:2019-03-14 17:06:40 【问题描述】:我正在使用 Spring 2.x、Spring Data REST、Hibernate 5.x、mysql 创建一个服务器 REST 应用程序。
我按照以下指南配置了多租户:https://dzone.com/articles/spring-boot-hibernate-multitenancy-implementation,唯一的区别是我为每个租户使用一个数据库。
我有一个MultiTenantConnectionProvider
用于创建与数据库的连接,还有一个TenantIdentifierResolver
用于获取当前租户。
一些相关的代码:
@Component
public class TenantIdentifierResolver implements CurrentTenantIdentifierResolver
@Override
public String resolveCurrentTenantIdentifier()
String tenantId = TenantContext.getCurrentTenant();
if (tenantId != null)
return tenantId;
return DEFAULT_TENANT_ID;
@Override
public boolean validateExistingCurrentSessions()
return true;
...
@Component
@Profile("prod")
public class MultiTenantConnectionProviderImpl implements MultiTenantConnectionProvider
private static final long serialVersionUID = 3193007611085791247L;
private Logger log = LogManager.getLogger();
private Map<String, HikariDataSource> dataSourceMap = new ConcurrentHashMap<String, HikariDataSource>();
@Autowired
private TenantRestClient tenantRestClient;
@Autowired
private PasswordEncrypt passwordEncrypt;
@Override
public void releaseAnyConnection(Connection connection) throws SQLException
connection.close();
@Override
public Connection getAnyConnection() throws SQLException
Connection connection = getDataSource(TenantIdResolver.TENANT_DEFAULT).getConnection();
return connection;
@Override
public Connection getConnection(String tenantId) throws SQLException
Connection connection = getDataSource(tenantId).getConnection();
return connection;
@Override
public void releaseConnection(String tenantId, Connection connection) throws SQLException
log.info("releaseConnection " + tenantId);
connection.close();
@Override
public boolean supportsAggressiveRelease()
return false;
@Override
public boolean isUnwrappableAs(Class unwrapType)
return false;
@Override
public <T> T unwrap(Class<T> unwrapType)
return null;
public HikariDataSource getDataSource(@NotNull String tentantId) throws SQLException
if (dataSourceMap.containsKey(tentantId))
return dataSourceMap.get(tentantId);
else
HikariDataSource dataSource = createDataSource(tentantId);
dataSourceMap.put(tentantId, dataSource);
return dataSource;
public HikariDataSource createDataSource(String tenantId) throws SQLException
log.info("Create Datasource for tenant ", tenantId);
try
Database database = tenantRestClient.getDatabase(tenantId);
DatabaseInstance databaseInstance = tenantRestClient.getDatabaseInstance(tenantId);
if (database != null && databaseInstance != null)
HikariConfig hikari = new HikariConfig();
String driver = "";
String options = "";
switch (databaseInstance.getType())
case MYSQL:
driver = "jdbc:mysql://";
options = "?useLegacyDatetimeCode=false&serverTimezone=UTC&useUnicode=yes&characterEncoding=UTF-8&useSSL=false";
break;
default:
driver = "jdbc:mysql://";
options = "?useLegacyDatetimeCode=false&serverTimezone=UTC&useUnicode=yes&characterEncoding=UTF-8&useSSL=false";
hikari.setJdbcUrl(driver + databaseInstance.getHost() + ":" + databaseInstance.getPort() + "/" + database.getName() + options);
hikari.setUsername(database.getUsername());
hikari.setPassword(passwordEncrypt.decryptPassword(database.getPassword()));
// MySQL optimizations, see
// https://github.com/brettwooldridge/HikariCP/wiki/MySQL-Configuration
hikari.addDataSourceProperty("cachePrepStmts", true);
hikari.addDataSourceProperty("prepStmtCacheSize", "250");
hikari.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
hikari.addDataSourceProperty("useServerPrepStmts", "true");
hikari.addDataSourceProperty("useLocalSessionState", "true");
hikari.addDataSourceProperty("useLocalTransactionState", "true");
hikari.addDataSourceProperty("rewriteBatchedStatements", "true");
hikari.addDataSourceProperty("cacheResultSetMetadata", "true");
hikari.addDataSourceProperty("cacheServerConfiguration", "true");
hikari.addDataSourceProperty("elideSetAutoCommits", "true");
hikari.addDataSourceProperty("maintainTimeStats", "false");
hikari.setMinimumIdle(3);
hikari.setMaximumPoolSize(5);
hikari.setIdleTimeout(30000);
hikari.setPoolName("JPAHikari_" + tenantId);
// mysql wait_timeout 600seconds
hikari.setMaxLifetime(580000);
hikari.setLeakDetectionThreshold(60 * 1000);
HikariDataSource dataSource = new HikariDataSource(hikari);
return dataSource;
else
throw new SQLException(String.format("DB not found for tenant %s!", tenantId));
catch (Exception e)
throw new SQLException(e.getMessage());
我也以这种方式配置了 Hibernate:
@Configuration
@Profile("prod")
public class HibernateConfig
@Autowired
private JpaProperties jpaProperties;
@Bean
public JpaVendorAdapter jpaVendorAdapter()
return new HibernateJpaVendorAdapter();
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource,
MultiTenantConnectionProvider multiTenantConnectionProviderImpl,
CurrentTenantIdentifierResolver currentTenantIdentifierResolverImpl)
Map<String, Object> properties = new HashMap<>();
properties.putAll(jpaProperties.getHibernateProperties(new HibernateSettings()));
properties.put(Environment.MULTI_TENANT, MultiTenancyStrategy.DATABASE);
properties.put(Environment.MULTI_TENANT_CONNECTION_PROVIDER, multiTenantConnectionProviderImpl);
properties.put(Environment.MULTI_TENANT_IDENTIFIER_RESOLVER, currentTenantIdentifierResolverImpl);
LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource);
em.setPackagesToScan("com.server");
em.setJpaVendorAdapter(jpaVendorAdapter());
em.setJpaPropertyMap(properties);
return em;
这是我的 application.properties:
spring.datasource.url=jdbc:mysql://url:3306/empty?useLegacyDatetimeCode=false&serverTimezone=UTC&useSSL=false
spring.datasource.username=empty
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.password=empty
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL57Dialect
spring.jpa.hibernate.ddl-auto: validate
spring.jpa.hibernate.naming.physical- strategy=org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
spring.jpa.show-sql: false
在应用程序启动期间,我看到 Spring 为我在属性文件中配置的数据库创建了一个连接池。
我想避免这种情况,因为我所有的连接都是由MultiTenantConnectionProviderImpl
创建的。
我想继续在我的 bean 中注入 EntityManager
和 Datasource
。
我已经看到如何禁用 Spring Boot 数据源配置 here,但是这样做我无法再在我的应用程序中注入数据源。
您对如何从属性文件中完全删除数据源的定义并从MultiTenantConnectionProviderImpl
以编程方式将数据源注入应用程序有什么建议吗?
【问题讨论】:
【参考方案1】:以下是有关如何以编程方式创建数据源的完整示例。
只是参数取自属性文件,而不是在 java 类中进行硬编码。
当您定义多个数据源时,您必须定义一个@Primary
,并且只定义一个,那么您将拥有一个标识每个数据源的@Qualifier
。如何管理它们应该很简单。
@RequiredArgsConstructor
@PropertySource("classpath:persistence/persistence-primarydb.properties")
@EnableJpaRepositories(basePackages = "io.vforge.cauldron.repository.primary",
entityManagerFactoryRef = "primaryEntityManagerFactory",
transactionManagerRef = "primaryTransactionManager")
@EnableJpaAuditing
@Configuration
public class CauldronPrimaryDatasource
private final Environment env;
@Primary
@Bean
public LocalContainerEntityManagerFactoryBean primaryEntityManagerFactory()
LocalContainerEntityManagerFactoryBean em
= new LocalContainerEntityManagerFactoryBean();
em.setDataSource(primaryDataSource());
em.setPackagesToScan("io.vforge.cauldron.model.primary");
HibernateJpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
HashMap<String, Object> properties = new HashMap<>();
properties.put("hibernate.hbm2ddl.auto", env.getProperty("primary.hibernate.hbm2ddl.auto"));
properties.put("hibernate.dialect", env.getProperty("primary.hibernate.dialect"));
properties.put("hibernate.show_sql", env.getProperty("primary.hibernate.show_sql"));
em.setJpaPropertyMap(properties);
return em;
@Primary
@Bean
public HikariDataSource primaryDataSource()
final HikariDataSource dataSource = new HikariDataSource();
dataSource.setJdbcUrl(env.getProperty("primary.datasource.url"));
dataSource.setUsername(env.getProperty("primary.datasource.username"));
dataSource.setPassword(env.getProperty("primary.datasource.password"));
return dataSource;
@Primary
@Bean
public PlatformTransactionManager primaryTransactionManager()
JpaTransactionManager transactionManager= new JpaTransactionManager();
transactionManager.setEntityManagerFactory(
primaryEntityManagerFactory().getObject());
return transactionManager;
【讨论】:
以上是关于在 Hibernate 多租户配置中禁用 Spring 数据源配置的主要内容,如果未能解决你的问题,请参考以下文章
Quartz 2 + Hibernate 4 多租户不稳定行为