Spring多租户架构中DataSource与Repository循环依赖问题
解决Spring多租户架构中的循环依赖问题
问题场景
在Spring应用中实现多租户架构时,通过ConfigurationDataSource类在启动时构建AbstractRoutingDataSource,代码如下:
import com.example.hikaricp_demo.domain.TenantConfig; import com.example.hikaricp_demo.repository.TenantConfigRepository; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; import javax.sql.DataSource; import java.util.HashMap; import java.util.List; import java.util.Map; @Configuration public class ConfigurationDataSource { @Autowired private TenantConfigRepository repository; private List<TenantConfig> getListTenant(){ return repository.findAll(); } TenantConfig tenantDefault() { return repository.findByTenantId("tenantDefault"); } @Bean public DataSource dataSource() { Map<Object, Object> resolvedDataSources = new HashMap<>(); List<TenantConfig> listTenant = getListTenant(); for (TenantConfig item : listTenant) { HikariConfig hikariConfig = new HikariConfig(); hikariConfig.setDriverClassName(item.getDriverClassName()); hikariConfig.setJdbcUrl(item.getUrl()); hikariConfig.setUsername(item.getUsername()); hikariConfig.setPassword(item.getPassword()); hikariConfig.setSchema(item.getSchema()); HikariDataSource dataSource = new HikariDataSource(hikariConfig); resolvedDataSources.put(item.getTenantId(), dataSource); } AbstractRoutingDataSource dataSource = new MultiTenantDataSource(); dataSource.setDefaultTargetDataSource(tenantDefault()); dataSource.setTargetDataSources(resolvedDataSources); dataSource.afterPropertiesSet(); return dataSource; } }
其中TenantConfigRepository是Spring Data JPA仓库,用于从数据库读取租户配置。启动时出现循环依赖错误:
Description: The dependencies of some of the beans in the application context form a cycle: entityManagerFactory defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class] ┌─────┐ | dataSourceScriptDatabaseInitializer defined in class path resource [org/springframework/boot/autoconfigure/sql/init/DataSourceInitializationConfiguration.class] ↑ ↓ | configurationDataSource (field private com.example.hikaricp_demo.repository.TenantConfigRepository com.example.hikaricp_demo.config.ConfigurationDataSource.repository) ↑ ↓ | tenantConfigRepository defined in com.example.hikaricp_demo.repository.TenantConfigRepository defined in @EnableJpaRepositories declared on JpaRepositoriesRegistrar.EnableJpaRepositoriesConfiguration ↑ ↓ | jpaSharedEM_entityManagerFactory └─────┘
原因分析
循环依赖的核心是:DataSource Bean需要TenantConfigRepository加载租户配置,而TenantConfigRepository作为JPA仓库,又依赖DataSource来初始化,形成闭环。
解决方案
通过分离系统数据源和多租户数据源打破循环:专门配置一个系统数据源用于存储租户配置,先初始化该数据源和对应的JPA组件,再用它加载租户配置构建多租户数据源。
1. 配置系统数据源(存储租户配置)
创建SystemDataSourceConfig类,单独配置连接租户配置库的数据源、EntityManagerFactory和事务管理器,与业务组件隔离:
import com.example.hikaricp_demo.domain.TenantConfig; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import org.springframework.transaction.PlatformTransactionManager; import javax.sql.DataSource; import java.util.HashMap; import java.util.Map; @Configuration @EnableJpaRepositories( basePackages = "com.example.hikaricp_demo.repository", entityManagerFactoryRef = "systemEntityManagerFactory", transactionManagerRef = "systemTransactionManager" ) public class SystemDataSourceConfig { @Primary @Bean @ConfigurationProperties("spring.datasource.system") public DataSourceProperties systemDataSourceProperties() { return new DataSourceProperties(); } @Primary @Bean public DataSource systemDataSource(@Qualifier("systemDataSourceProperties") DataSourceProperties properties) { return properties.initializeDataSourceBuilder().build(); } @Primary @Bean(name = "systemEntityManagerFactory") public LocalContainerEntityManagerFactoryBean systemEntityManagerFactory( EntityManagerFactoryBuilder builder, @Qualifier("systemDataSource") DataSource dataSource) { Map<String, String> jpaProperties = new HashMap<>(); jpaProperties.put("hibernate.hbm2ddl.auto", "update"); return builder .dataSource(dataSource) .packages(TenantConfig.class) .persistenceUnit("system") .properties(jpaProperties) .build(); } @Primary @Bean(name = "systemTransactionManager") public PlatformTransactionManager systemTransactionManager( @Qualifier("systemEntityManagerFactory") LocalContainerEntityManagerFactoryBean systemEntityManagerFactory) { return new JpaTransactionManager(systemEntityManagerFactory.getObject()); } }
在配置文件中添加系统数据源的连接信息(以YAML为例):
spring: datasource: system: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/tenant_config_db?useSSL=false&serverTimezone=UTC username: root password: root
2. 修正多租户数据源配置
修改ConfigurationDataSource,修复默认数据源的错误(原代码直接传入TenantConfig对象),并确保注入系统数据源对应的TenantConfigRepository:
import com.example.hikaricp_demo.domain.TenantConfig; import com.example.hikaricp_demo.repository.TenantConfigRepository; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; import javax.sql.DataSource; import java.util.HashMap; import java.util.List; import java.util.Map; @Configuration public class ConfigurationDataSource { @Autowired private TenantConfigRepository repository; private List<TenantConfig> getListTenant(){ return repository.findAll(); } TenantConfig tenantDefault() { return repository.findByTenantId("tenantDefault"); } @Bean(name = "multiTenantDataSource") public DataSource dataSource() { Map<Object, Object> resolvedDataSources = new HashMap<>(); List<TenantConfig> listTenant = getListTenant(); for (TenantConfig item : listTenant) { HikariConfig hikariConfig = new HikariConfig(); hikariConfig.setDriverClassName(item.getDriverClassName()); hikariConfig.setJdbcUrl(item.getUrl()); hikariConfig.setUsername(item.getUsername()); hikariConfig.setPassword(item.getPassword()); hikariConfig.setSchema(item.getSchema()); HikariDataSource dataSource = new HikariDataSource(hikariConfig); resolvedDataSources.put(item.getTenantId(), dataSource); } AbstractRoutingDataSource dataSource = new MultiTenantDataSource(); // 为默认租户创建对应的DataSource TenantConfig defaultTenant = tenantDefault(); HikariConfig defaultConfig = new HikariConfig(); defaultConfig.setDriverClassName(defaultTenant.getDriverClassName()); defaultConfig.setJdbcUrl(defaultTenant.getUrl()); defaultConfig.setUsername(defaultTenant.getUsername()); defaultConfig.setPassword(defaultTenant.getPassword()); defaultConfig.setSchema(defaultTenant.getSchema()); dataSource.setDefaultTargetDataSource(new HikariDataSource(defaultConfig)); dataSource.setTargetDataSources(resolvedDataSources); dataSource.afterPropertiesSet(); return dataSource; } }
3. 配置业务JPA组件使用多租户数据源
创建BusinessJpaConfig,让业务Repository和实体类使用多租户数据源:
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; import org.springframework.orm.jpa.JpaTransactionManager; import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean; import org.springframework.transaction.PlatformTransactionManager; import javax.sql.DataSource; import java.util.HashMap; import java.util.Map; @Configuration @EnableJpaRepositories( basePackages = "com.example.hikaricp_demo.business.repository", entityManagerFactoryRef = "businessEntityManagerFactory", transactionManagerRef = "businessTransactionManager" ) public class BusinessJpaConfig { @Bean(name = "businessEntityManagerFactory") public LocalContainerEntityManagerFactoryBean businessEntityManagerFactory( EntityManagerFactoryBuilder builder, @Qualifier("multiTenantDataSource") DataSource dataSource) { Map<String, String> jpaProperties = new HashMap<>(); return builder .dataSource(dataSource) .packages("com.example.hikaricp_demo.business.domain") .persistenceUnit("business") .properties(jpaProperties) .build(); } @Bean(name = "businessTransactionManager") public PlatformTransactionManager businessTransactionManager( @Qualifier("businessEntityManagerFactory") LocalContainerEntityManagerFactoryBean businessEntityManagerFactory) { return new JpaTransactionManager(businessEntityManagerFactory.getObject()); } }
原理说明
通过分离系统数据源和多租户数据源,构建了线性的依赖链:
- 系统数据源先初始化 → 初始化
TenantConfigRepository - 用
TenantConfigRepository加载租户配置 → 构建多租户数据源 - 业务JPA组件依赖多租户数据源完成初始化
彻底打破了原有的循环依赖,同时修正了原代码中默认数据源设置错误的问题。
内容的提问来源于stack exchange,提问作者Thanh Dat Truong
相关产品推荐
相关产品推荐

