Spring Boot升级至3.1.5后Hibernate 6.2基于Schema的多租户失效
解决Spring Boot 3.1.5 + Hibernate 6.2 基于Schema的多租户配置问题
针对你升级后遇到的Connection is closed错误及多租户配置失效问题,核心原因是Hibernate 6对多租户组件的生命周期管理逻辑变更,加上Spring Boot 3.x自动配置顺序调整,导致连接提供者在依赖的配置Bean初始化完成前就被调用,进而产生无效连接。以下是具体解决方案:
1. 调整多租户连接提供者的依赖注入方式
将连接提供者依赖的自定义配置Bean改为延迟加载,避免初始化阶段依赖未就绪的Bean:
import jakarta.inject.Provider; import org.hibernate.engine.jdbc.connections.spi.MultiTenantConnectionProvider; import org.springframework.stereotype.Component; import org.springframework.beans.factory.annotation.Autowired; import javax.sql.DataSource; import java.sql.Connection; import java.sql.SQLException; @Component @Lazy public class TenantBasedConnectionProvider implements MultiTenantConnectionProvider { // 用Provider延迟获取配置Bean,而非直接注入 private final Provider<YourCustomConfigBean> configBeanProvider; private final DataSource dataSource; @Autowired public TenantBasedConnectionProvider(Provider<YourCustomConfigBean> configBeanProvider, DataSource dataSource) { this.configBeanProvider = configBeanProvider; this.dataSource = dataSource; } @Override public Connection getConnection(String tenantIdentifier) throws SQLException { // 真正需要时才获取配置Bean实例 YourCustomConfigBean config = configBeanProvider.get(); String targetSchema = config.getSchemaForTenant(tenantIdentifier); Connection connection = dataSource.getConnection(); // 设置租户Schema connection.setSchema(targetSchema); return connection; } @Override public void releaseConnection(String tenantIdentifier, Connection connection) throws SQLException { // 重置Schema到默认值(如public),再释放连接 connection.setSchema("public"); connection.close(); } @Override public boolean supportsAggressiveRelease() { return false; } @Override public boolean isUnwrappableAs(Class<?> unwrapType) { return MultiTenantConnectionProvider.class.isAssignableFrom(unwrapType); } @Override public <T> T unwrap(Class<T> unwrapType) { return unwrapType.cast(this); } }
2. 正确注册多租户组件到Hibernate
放弃旧版通过HibernatePropertiesCustomizer硬塞组件的方式,改用Spring Boot 3.x兼容的EntityManagerFactory配置:
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter; import javax.sql.DataSource; import java.util.Map; @Configuration public class HibernateMultiTenantConfig { @Bean public LocalContainerEntityManagerFactoryBean entityManagerFactory( DataSource dataSource, TenantBasedConnectionProvider connectionProvider, YourTenantIdentifierResolver tenantResolver) { LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean(); em.setDataSource(dataSource); em.setPackagesToScan("your.entity.package.path"); // 替换为你的实体类包路径 em.setJpaVendorAdapter(new HibernateJpaVendorAdapter()); // 配置Hibernate多租户属性 em.setJpaPropertyMap(Map.of( org.hibernate.cfg.AvailableSettings.MULTI_TENANT_CONNECTION_PROVIDER, connectionProvider, org.hibernate.cfg.AvailableSettings.MULTI_TENANT_IDENTIFIER_RESOLVER, tenantResolver )); return em; } }
3. 修复连接生命周期管理逻辑
Hibernate 6对连接的释放和复用要求更严格,确保:
- 每次获取连接都从Spring管理的数据源中获取,不要自行缓存连接
- 释放连接前重置Schema到默认值,避免后续连接被污染
- 严格实现
MultiTenantConnectionProvider的所有接口方法,尤其是supportsAggressiveRelease返回false,让Hibernate管理连接生命周期
4. 调整Testcontainers测试配置
在测试阶段提前初始化租户Schema,并确保租户标识符正确设置:
import org.springframework.test.context.jdbc.Sql; import org.junit.jupiter.api.BeforeEach; @Sql(executionPhase = Sql.ExecutionPhase.BEFORE_TEST_CLASS, scripts = "classpath:init-tenant-schemas.sql") public class TestContainerTests { @BeforeEach void setupTenant() { // 设置当前租户标识符(替换为你的租户上下文工具类) TenantContext.setCurrentTenant("test-tenant-1"); } // 测试方法... }
其中init-tenant-schemas.sql包含创建测试所需Schema的SQL语句:
CREATE SCHEMA IF NOT EXISTS test-tenant-1; CREATE SCHEMA IF NOT EXISTS test-tenant-2;
5. 确保配置Bean的属性加载优先级
通过@ConfigurationProperties配合@EnableConfigurationProperties确保YAML配置被正确绑定,避免配置加载滞后:
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableConfigurationProperties; import java.util.Map; @Configuration @EnableConfigurationProperties(TenantSchemaProperties.class) public class YourCustomConfigBean { private final TenantSchemaProperties schemaProperties; public YourCustomConfigBean(TenantSchemaProperties schemaProperties) { this.schemaProperties = schemaProperties; } public String getSchemaForTenant(String tenantId) { return schemaProperties.getMapping().get(tenantId); } } @ConfigurationProperties(prefix = "tenant.schemas") public class TenantSchemaProperties { private Map<String, String> mapping; // 对应YAML中的租户-Schema映射 // getter/setter }
内容的提问来源于stack exchange,提问作者NoID
相关产品推荐
相关产品推荐

