SpringBoot+Hibernate多租户数据源切换失效问题求助
Hibernate多租户未切换数据源问题排查与解决
问题核心
自定义DataSourceBasedMultiTenantConnectionProviderImpl未被Hibernate调用,导致过滤器设置租户ID后,查询仍使用默认数据源,触发PostgreSQL表不存在错误。同时项目混用MyBatis,需确保MyBatis也遵循Hibernate多租户上下文。
关键修复步骤
1. 移除Hibernate配置中手动设置的数据源
原配置中em.setDataSource(dataSource)会覆盖多租户连接提供者的逻辑,导致自定义类无法被调用。修改HibernateConfig:
@Configuration @EnableTransactionManagement @EnableJpaRepositories(basePackages = {"com.xyz.*"}) public class HibernateConfig { @Autowired private JpaProperties jpaProperties; @Bean JpaVendorAdapter jpaVendorAdapter() { return new HibernateJpaVendorAdapter(); } @Bean(name = "entityManagerFactory") public LocalContainerEntityManagerFactoryBean entityManagerFactory( MultiTenantConnectionProvider multiTenantConnectionProviderImpl, CurrentTenantIdentifierResolver currentTenantIdentifierResolverImpl) { Map<String, Object> jpaPropertiesMap = new HashMap<>(jpaProperties.getProperties()); jpaPropertiesMap.put("hibernate.multiTenancy", MultiTenancyStrategy.SCHEMA); jpaPropertiesMap.put("hibernate.multi_tenant_connection_provider", multiTenantConnectionProviderImpl); jpaPropertiesMap.put("hibernate.tenant_identifier_resolver", currentTenantIdentifierResolverImpl); jpaPropertiesMap.put("hibernate.format_sql", true); jpaPropertiesMap.put("hibernate.show_sql", true); LocalContainerEntityManagerFactoryBean em = new LocalContainerEntityManagerFactoryBean(); // 移除手动设置数据源,交由多租户连接提供者管理 // em.setDataSource(dataSource); em.setJpaVendorAdapter(jpaVendorAdapter()); em.setJpaPropertyMap(jpaPropertiesMap); em.setPackagesToScan("com.xyz.*"); return em; } }
2. 确保租户ID解析器正确注册与上下文传递
实现CurrentTenantIdentifierResolver并注入Spring容器,通过ThreadLocal存储租户ID:
@Component public class TenantIdentifierResolver implements CurrentTenantIdentifierResolver { private static final ThreadLocal<String> CURRENT_TENANT = new ThreadLocal<>(); private static final String DEFAULT_TENANT = "master"; public static void setCurrentTenant(String tenantId) { CURRENT_TENANT.set(tenantId); } public static void clear() { CURRENT_TENANT.remove(); } @Override public String resolveCurrentTenantIdentifier() { String tenantId = CURRENT_TENANT.get(); return tenantId != null ? tenantId : DEFAULT_TENANT; } @Override public boolean validateExistingCurrentSessions() { return true; } }
过滤器中正确设置租户ID:
@Component public class TenantFilter extends OncePerRequestFilter { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { // 从请求头/参数获取租户ID,示例从header读取 String tenantId = request.getHeader("X-Tenant-ID"); TenantIdentifierResolver.setCurrentTenant(tenantId); try { filterChain.doFilter(request, response); } finally { // 请求结束后清理上下文,避免线程污染 TenantIdentifierResolver.clear(); } } }
3. 让MyBatis适配Hibernate多租户上下文
MyBatis默认不会使用Hibernate的多租户连接,需自定义租户感知数据源:
@Component public class HibernateTenantAwareDataSource implements DataSource { @PersistenceContext private EntityManager entityManager; @Override public Connection getConnection() throws SQLException { // 从Hibernate Session获取租户连接 return entityManager.unwrap(Session.class).connection(); } @Override public Connection getConnection(String username, String password) throws SQLException { return getConnection(); } // 以下方法按需实现,可委托给连接对象或默认实现 @Override public <T> T unwrap(Class<T> iface) throws SQLException { return entityManager.unwrap(iface); } @Override public boolean isWrapperFor(Class<?> iface) throws SQLException { return entityManager.isWrapperFor(iface); } @Override public PrintWriter getLogWriter() throws SQLException { return getConnection().getLogWriter(); } @Override public void setLogWriter(PrintWriter out) throws SQLException { getConnection().setLogWriter(out); } @Override public void setLoginTimeout(int seconds) throws SQLException { getConnection().setLoginTimeout(seconds); } @Override public int getLoginTimeout() throws SQLException { return getConnection().getLoginTimeout(); } @Override public Logger getParentLogger() throws SQLFeatureNotSupportedException { return Logger.getLogger(Logger.GLOBAL_LOGGER_NAME); } }
修改MyBatis配置使用该数据源:
@Configuration @MapperScan(basePackages = "com.xyz.mapper") public class MyBatisConfig { @Autowired private HibernateTenantAwareDataSource tenantAwareDataSource; @Bean public SqlSessionFactory sqlSessionFactory() throws Exception { SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean(); sessionFactory.setDataSource(tenantAwareDataSource); // 配置mapper位置等其他参数 return sessionFactory.getObject(); } }
验证检查点
- 确认
DataSourceBasedMultiTenantConnectionProviderImpl和TenantIdentifierResolver都被Spring扫描并注册为Bean - 启动项目后,断点调试
selectDataSource方法,确认查询时会进入该方法 - 检查租户ID在过滤器中是否正确设置到ThreadLocal,Resolver能正确读取
内容的提问来源于stack exchange,提问作者Rishabh Sharma
相关产品推荐
相关产品推荐

