当前位置: 首页 > 知识库问答 >
问题:

在Hibernate多租户配置中禁用Spring数据源配置

闾丘英悟
2023-03-14

我正在使用Spring2.x、Spring Data REST、Hibernate5.x、MySQL创建一个服务器REST应用程序。

我按照以下准则配置了多租户:https://dzone.com/articles/spring-boot-hibernate-multitenancy-implementation,唯一的区别是我使用了每个租户一个数据库。

我有一个MultiTenantConnectionProvider来创建到DB的连接,还有一个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());
        }
    }

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

}
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创建了一个连接池,连接到我在属性文件中配置的db。

我希望避免这种情况,因为我的所有连接都是由MultiTenantConnectionProviderImpl创建的。我希望在bean中继续使用注入EntityManagerDataSource的功能。

我已经在这里看到了如何禁用Spring Boot数据源配置,但是这样做我就不能再在应用程序中注入数据源了。

对于如何从属性文件中完全删除数据源的定义,并以编程方式从MultiTenantConnectionProviderImpl在应用程序中注入数据源,您有什么建议吗?

共有1个答案

叶举
2023-03-14

下面是关于如何以编程方式创建数据源的完整示例。

只是参数取自属性文件,而不是在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;
    }

}
 类似资料:
  • 问题内容: 我正在尝试使用此网络研讨会中概述的单独的架构方法向Java应用程序添加多租户 我想知道如何通过spring来配置多个数据源,也许是通过使用属性文件并基于租户id从spring上下文中获取数据源。 更重要的是,尽管我希望能够配置支持此多租户功能的自定义连接提供程序实现,以供Hibernate使用,而不是默认使用的注入功能。 我怎样才能做到这一点。 问题答案: 使用。

  • 我试图设置一个配置来管理一个多租户环境,使用sping-start、sping-data-jpa、hibernate和mysql(相同的模式,每个租户表都有一个tenant_code列)。对于依赖项,父maven项目是sping-boo-starter-父(2.1.2.发布)。 在保存实体hibernate抛出此异常:org.hibernate.StaleObjectStateExcture:行被

  • 我有一个KeyClope实例,为每个域创建了两个域和一个用户。 我有我的spring boot应用程序。yml(resource server-API)用于一个特定领域,并在我的代码中修复。 它正在为Realm1工作并进行验证。 但现在我可以接收来自user2(tenant2)的请求,而令牌将无效,因为公钥(realm1)对于已签名的请求jwt令牌(realm2)无效。 允许多领域多租户和动态配置

  • 问题内容: 在Spring 3应用程序中,我试图通过Hibernate 4的本机MultiTenantConnectionProvider和CurrentTenantIdentifierResolver实现多租户。我发现在Hibernate 4.1.3 中存在此问题,但是我正在运行4.1.9并仍收到类似的异常: 以下是相关代码。在I中,我现在只写了一些简单的代码,每次都只返回一个新的连接,并且在这

  • 问题内容: 在Spring 3应用程序中,我试图通过Hibernate 4的本机MultiTenantConnectionProvider和CurrentTenantIdentifierResolver实现多租户。我看到在Hibernate 4.1.3 中存在此问题,但是我正在运行4.1.9并仍然收到类似的异常: 以下是相关代码。在I中,我现在只写了一些哑代码,每次都只返回一个新的连接,并且此时总

  • 问题内容: 目前,我正在使用带有@Transactional批注的DriverManagerDataSource来管理事务。但是所有事务都非常非常慢,这可能是因为数据源每次都打开和关闭与db的连接。 我应该使用什么数据源来加快交易速度? 问题答案: 实际上不是连接池,只能用于测试。您应该尝试使用Apache Commons DBCP 。就像是: