【问题标题】:Spring Boot + Hibernate + JPA + Postgres Multi tenant App unable to persist entitySpring Boot + Hibernate + JPA + Postgres 多租户应用程序无法持久化实体
【发布时间】:2019-10-08 05:06:24
【问题描述】:

我正在使用具有多个架构的单个数据库构建多租户 saas 应用程序;每个客户端一个模式。我正在使用 Spring Boot 2.1.5、Hibernate 5.3.10 以及兼容的 spring data jpa 和 postgres 11.2。

我已经关注了这篇博文https://dzone.com/articles/spring-boot-hibernate-multitenancy-implementation

尝试调试代码,以下是我的发现:
* 对于数据源配置中提供的默认模式,hibernate 会正确验证模式。它在默认模式中创建缺失或新的表/实体。
* 租户标识符被正确解析,休眠使用此租户建立会话。

我已将代码上传到以下仓库:

https://github.com/naveentulsi/multitenant-lithium

我在这里添加了一些重要的类。

    @Component
    @Log4j2
    public class MultiTenantConnectionProviderImpl implements 
       MultiTenantConnectionProvider {

    @Autowired
    DataSource dataSource;

    @Override
    public Connection getAnyConnection() throws SQLException {
        return dataSource.getConnection();
    }

    @Override
    public void releaseAnyConnection(Connection connection) throws SQLException {
        connection.close();
    }

    @Override
    public Connection getConnection(String tenantIdentifier) throws SQLException {
        final Connection connection = getAnyConnection();
        try {
            if (!StringUtils.isEmpty(tenantIdentifier)) {
                String setTenantQuery = String.format(AppConstants.SCHEMA_CHANGE_QUERY, tenantIdentifier);
                connection.createStatement().execute(setTenantQuery);
                final ResultSet resultSet = connection.createStatement().executeQuery("select current_schema()");
                if(resultSet != null){
                    final String string = resultSet.getString(1);
                    log.info("Current Schema" + string);
                }
                System.out.println("Statement execution");
            } else {
                connection.createStatement().execute(String.format(AppConstants.SCHEMA_CHANGE_QUERY, AppConstants.DEFAULT_SCHEMA));
            }
        } catch (SQLException se) {
            throw new HibernateException(
                    "Could not change schema for connection [" + tenantIdentifier + "]",
                    se
            );
        }
        return connection;
    }

    @Override
    public void releaseConnection(String tenantIdentifier, Connection connection) throws SQLException {
        try {
            String Query = String.format(AppConstants.DEFAULT_SCHEMA, tenantIdentifier);
            connection.createStatement().executeQuery(Query);
        } catch (SQLException se) {
            throw new HibernateException(
                    "Could not change schema for connection [" + tenantIdentifier + "]",
                    se
            );
        }
        connection.close();
    }

    @Override
    public boolean supportsAggressiveRelease() {
        return true;
    }

    @Override
    public boolean isUnwrappableAs(Class unwrapType) {
        return false;
    }

    @Override
    public <T> T unwrap(Class<T> unwrapType) {
        return null;
    }
}
@Configuration
@EnableJpaRepositories
public class ApplicationConfiguration implements WebMvcConfigurer {

    @Autowired
    JpaProperties jpaProperties;

    @Autowired
    TenantInterceptor tenantInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(tenantInterceptor);
    }


    @Bean
    public DataSource dataSource() {
        return DataSourceBuilder.create().username(AppConstants.USERNAME).password(AppConstants.PASS)
                .url(AppConstants.URL)
                .driverClassName("org.postgresql.Driver").build();
    }

    @Bean
    public JpaVendorAdapter jpaVendorAdapter() {
        return new HibernateJpaVendorAdapter();
    }

    @Bean
    public LocalContainerEntityManagerFactoryBean entityManagerFactory(DataSource dataSource, MultiTenantConnectionProviderImpl multiTenantConnectionProviderImpl, CurrentTenantIdentifierResolver currentTenantIdentifierResolver) {
        Map<String, Object> properties = new HashMap<>();

        properties.put("hibernate.dialect", "org.hibernate.dialect.PostgreSQLDialect");
        properties.put("hibernate.hbm2ddl.auto", "update");
        properties.put("hibernate.ddl-auto", "update");
        properties.put("hibernate.jdbc.lob.non_contextual_creation", "true");
        properties.put("show-sql", "true");
        properties.put("hikari.maximum-pool-size", "3");
        properties.put("hibernate.default_schema", "master");
        properties.put("maximum-pool-size", "2");

        if (dataSource instanceof HikariDataSource) {
            ((HikariDataSource) dataSource).setMaximumPoolSize(3);
        }

        properties.put(Environment.MULTI_TENANT, MultiTenancyStrategy.SCHEMA);
        properties.put(Environment.MULTI_TENANT_CONNECTION_PROVIDER, multiTenantConnectionProviderImpl);
        properties.put(Environment.MULTI_TENANT_IDENTIFIER_RESOLVER, currentTenantIdentifierResolver);
        properties.put(Environment.FORMAT_SQL, true);


        LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean();
        em.setDataSource(dataSource);

        em.setPackagesToScan("com.saas");
        em.setJpaVendorAdapter(jpaVendorAdapter());
        em.setJpaPropertyMap(properties);

        return em;
    }
}
@Component
public class TenantResolver implements CurrentTenantIdentifierResolver {


    private static final ThreadLocal<String> TENANT_IDENTIFIER = new ThreadLocal<>();

    public static void setTenantIdentifier(String tenantIdentifier) {
        TENANT_IDENTIFIER.set(tenantIdentifier);
    }

    public static void reset() {
        TENANT_IDENTIFIER.remove();
    }

    @Override
    public String resolveCurrentTenantIdentifier() {
        String currentTenant = TENANT_IDENTIFIER.get() != null ? TENANT_IDENTIFIER.get() : AppConstants.DEFAULT_SCHEMA;
        return currentTenant;
    }

    @Override
    public boolean validateExistingCurrentSessions() {
        return true;
    }
}

在 TenantResolver 成功注入 TenantId 后,entityManager 应该能够将实体存储到数据库中相应的租户模式中。也就是说,如果我们创建一个实体的对象并在db中持久化,它应该成功保存在db中。但就我而言,实体不会保存到默认模式以外的任何模式中。

更新 1:我能够使用 mysql 8.0.12 进行多租户模式切换。使用 postgres 仍然无法做到这一点。

【问题讨论】:

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


    【解决方案1】:

    在你的类“ApplicationConfiguration.java”中;

    您必须删除此“properties.put("hibernate.default_schema", "master");",为什么因为当您更改架构时它能够更改,但是当它一次又一次到达此行时设置默认架构

    希望你能得到答案

    谢谢大家

    保重!

    【讨论】:

      【解决方案2】:

      你应该使用 AbstractRoutingDataSource 来实现这一点,它在幕后完成了所有的魔法,网上有很多例子,你可以在https://www.baeldung.com/spring-abstract-routing-data-source找到一个

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-09-22
        • 2018-01-05
        • 2021-06-26
        • 2019-11-28
        • 2021-05-06
        • 2020-02-21
        • 1970-01-01
        • 2012-11-08
        相关资源
        最近更新 更多