为新数据源生成架构

时间:2018-06-02 21:42:15

标签: java hibernate spring-boot spring-data-jpa multi-tenant

现状

我设法使用Spring Boot和Hibernate设置单独数据库多租户,使用将用户映射到租户的主数据库。

问题

当我在运行时添加新租户时,我运行一个工作正常的create database命令。然而,尽管创建了(Hikari)数据源,但hibernate在此之后并没有生成模式。

我已经包含了多租户的所有配置类。我发现了这个类似的问题(有解决方案)here,但我无法理解他们的解决方案,也不知道它是否适用于我的解决方案。 (特别是因为我使用纯java配置而没有xml)。

DatasourceUtil.java

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;
    }
}

DataSourceBasedMultiTenantConnectionProviderImpl.java(满口)

@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);
    }
}

TenantDatabaseConfig.java

@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;
    }
}

0 个答案:

没有答案