我设法使用Spring Boot和Hibernate设置单独数据库多租户,使用将用户映射到租户的主数据库。
当我在运行时添加新租户时,我运行一个工作正常的create database命令。然而,尽管创建了(Hikari)数据源,但hibernate在此之后并没有生成模式。
我已经包含了多租户的所有配置类。我发现了这个类似的问题(有解决方案)here,但我无法理解他们的解决方案,也不知道它是否适用于我的解决方案。 (特别是因为我使用纯java配置而没有xml)。
public class DataSourceUtil {
private static final Logger LOG = LoggerFactory
.getLogger(DataSourceUtil.class);
/**
* Utility method to create and configure a data source
*
* @param masterTenant
* @return
*/
public static DataSource createAndConfigureDataSource(
School masterTenant) {
// Create if not exists
try {
Connection con = DriverManager.getConnection(masterTenant.getServerAddress(), masterTenant.getUsername(), masterTenant.getPassword());
String sql = "IF NOT EXISTS(SELECT * FROM sys.databases WHERE name = '"+masterTenant.getCode()+"' )\n" +
" BEGIN\n" +
" CREATE DATABASE ["+masterTenant.getCode()+"] \n" +
" END";
Statement s = con.createStatement();
s.execute(sql);
s.close();
con.close();
} catch (Exception ex) {
System.out.println(ex);
}
HikariDataSource ds = new HikariDataSource();
ds.setUsername(masterTenant.getUsername());
ds.setPassword(masterTenant.getPassword());
ds.setJdbcUrl(masterTenant.getDBAddress());
ds.setDriverClassName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
// HikariCP settings - could come from the master_tenant table but
// hardcoded here for brevity
// Maximum waiting time for a connection from the pool
ds.setConnectionTimeout(20000);
// Minimum number of idle connections in the pool
ds.setMinimumIdle(10);
// Maximum number of actual connection in the pool
ds.setMaximumPoolSize(20);
// Maximum time that a connection is allowed to sit idle in the pool
ds.setIdleTimeout(300000);
ds.setConnectionTimeout(20000);
// Setting up a pool name for each tenant datasource
String tenantId = masterTenant.getCode();
String tenantConnectionPoolName = tenantId + "-connection-pool";
ds.setPoolName(tenantConnectionPoolName);
LOG.info("Configured datasource:" + masterTenant.getCode()
+ ". Connection poolname:" + tenantConnectionPoolName);
return ds;
}
}
@Configuration
public class DataSourceBasedMultiTenantConnectionProviderImpl
extends AbstractDataSourceBasedMultiTenantConnectionProviderImpl {
private static final Logger LOG = LoggerFactory
.getLogger(DataSourceBasedMultiTenantConnectionProviderImpl.class);
private static final long serialVersionUID = 1L;
/**
* Injected MasterTenantRepository to access the tenant information from the
* master_tenant table
*/
@Autowired
private SchoolRepository schoolRepository;
/**
* Map to store the tenant ids as key and the data source as the value
*/
private Map<String, DataSource> dataSourcesMtApp = new TreeMap<>();
@Override
protected DataSource selectAnyDataSource() {
// This method is called more than once. So check if the data source map
// is empty. If it is then rescan master_tenant table for all tenant
// entries.
if (dataSourcesMtApp.isEmpty()) {
List<School> masterTenants = schoolRepository.findAll();
if (masterTenants.size() == 0) masterTenants = initialiseTenants();
LOG.info(">>>> selectAnyDataSource() -- Total tenants:"
+ masterTenants.size());
for (School masterTenant : masterTenants) {
dataSourcesMtApp.put(masterTenant.getCode(), DataSourceUtil
.createAndConfigureDataSource(masterTenant));
}
}
return this.dataSourcesMtApp.values().iterator().next();
}
private List<School> initialiseTenants() {
schoolRepository.save(new School("OLSP","jdbc:sqlserver://localhost:1433", "sa", "heyvsaucemichaelhere", "Our Lady and St Patrick's"));
return schoolRepository.findAll();
}
@Override
protected DataSource selectDataSource(String tenantIdentifier) {
// If the requested tenant id is not present check for it in the master
// database 'master_tenant' table
if (!this.dataSourcesMtApp.containsKey(tenantIdentifier)) {
List<School> masterTenants = schoolRepository.findAll();
LOG.info(">>>> selectDataSource() -- tenant:" + tenantIdentifier
+ " Total tenants:" + masterTenants.size());
for (School masterTenant : masterTenants) {
dataSourcesMtApp.put(masterTenant.getCode(), DataSourceUtil
.createAndConfigureDataSource(masterTenant));
}
}
return this.dataSourcesMtApp.get(tenantIdentifier);
}
}
@Configuration
@EnableTransactionManagement
@ComponentScan(basePackages = { "caselight.tenant.repository",
"caselight.tenant.entities" })
@EnableJpaRepositories(basePackages = {
"caselight.tenant.repository",
"caselight.tenant.service" },
entityManagerFactoryRef = "tenantEntityManagerFactory",
transactionManagerRef = "tenantTransactionManager")
public class TenantDatabaseConfig {
private static final Logger LOG = LoggerFactory
.getLogger(TenantDatabaseConfig.class);
@Bean(name = "tenantJpaVendorAdapter")
public JpaVendorAdapter jpaVendorAdapter() {
return new HibernateJpaVendorAdapter();
}
@Bean(name = "tenantTransactionManager")
public JpaTransactionManager transactionManager(
EntityManagerFactory tenantEntityManager) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(tenantEntityManager);
return transactionManager;
}
/**
* The multi tenant connection provider
*
* @return
*/
@Bean(name = "datasourceBasedMultitenantConnectionProvider")
@ConditionalOnBean(name = "masterEntityManagerFactory")
public MultiTenantConnectionProvider multiTenantConnectionProvider() {
// Autowires the multi connection provider
return new DataSourceBasedMultiTenantConnectionProviderImpl();
}
/**
* The current tenant identifier resolver
*
* @return
*/
@Bean(name = "currentTenantIdentifierResolver")
public CurrentTenantIdentifierResolver currentTenantIdentifierResolver() {
return new CurrentTenantIdentifierResolverImpl();
}
/**
* Creates the entity manager factory bean which is required to access the
* JPA functionalities provided by the JPA persistence provider, i.e.
* Hibernate in this case.
*
* @param connectionProvider
* @param tenantResolver
* @return
*/
@Bean(name = "tenantEntityManagerFactory")
@ConditionalOnBean(name = "datasourceBasedMultitenantConnectionProvider")
public LocalContainerEntityManagerFactoryBean entityManagerFactory(
@Qualifier("datasourceBasedMultitenantConnectionProvider")
MultiTenantConnectionProvider connectionProvider,
@Qualifier("currentTenantIdentifierResolver")
CurrentTenantIdentifierResolver tenantResolver) {
LocalContainerEntityManagerFactoryBean emfBean = new LocalContainerEntityManagerFactoryBean();
//All tenant related entities, repositories and service classes must be scanned
emfBean.setPackagesToScan(
new String[] { RandomCity.class.getPackage().getName(),
RandomCityRepository.class.getPackage().getName(),
GenericService.class.getPackage().getName() });
emfBean.setJpaVendorAdapter(jpaVendorAdapter());
emfBean.setPersistenceUnitName("tenantdb-persistence-unit");
Map<String, Object> properties = new HashMap<>();
properties.put(org.hibernate.cfg.Environment.MULTI_TENANT,
MultiTenancyStrategy.DATABASE);
properties.put(
org.hibernate.cfg.Environment.MULTI_TENANT_CONNECTION_PROVIDER,
connectionProvider);
properties.put(
org.hibernate.cfg.Environment.MULTI_TENANT_IDENTIFIER_RESOLVER,
tenantResolver);
properties.put(org.hibernate.cfg.Environment.DIALECT,
"org.hibernate.dialect.SQLServer2012Dialect");
properties.put(org.hibernate.cfg.Environment.SHOW_SQL, true);
properties.put(org.hibernate.cfg.Environment.FORMAT_SQL, true);
properties.put(org.hibernate.cfg.Environment.HBM2DDL_AUTO, "update");
emfBean.setJpaPropertyMap(properties);
LOG.info("tenantEntityManagerFactory set up successfully!");
return emfBean;
}
}