Spring Data JPA基于外部库实体配置主库的循环依赖问题
解决主数据源与公共凭证库EntityManager的循环依赖问题
问题根源
循环依赖的核心原因是:主数据源初始化时需要通过公共库的JPA EntityManager/Repository获取连接凭证,但公共库的EntityManager配置依赖Spring容器初始化,而主数据源作为@Primary Bean启动优先级高,导致双方互相等待对方初始化完成,形成死锁。
解决方案:绕过JPA,直接用JDBC获取凭证
公共凭证库的连接信息是固定的,不需要JPA的ORM能力,直接用JDBC/JdbcTemplate查询凭证,完全避开对EntityManager的依赖,彻底打破循环。
步骤1:配置公共库的独立JDBC Template
修改AssinanteDataSourceConfig,新增一个仅用于查询凭证的JDBC Template Bean,保留原有JPA配置供其他业务使用:
@Configuration public class AssinanteDataSourceConfig { // 公共凭证库数据源(配置从固定渠道读取,比如application.properties) @Bean(name = "assinanteDataSource") public DataSource assinanteDataSource(Environment env) { HikariDataSource dataSource = new HikariDataSource(); dataSource.setJdbcUrl(env.getProperty("assinante.db.url")); dataSource.setUsername(env.getProperty("assinante.db.username")); dataSource.setPassword(env.getProperty("assinante.db.password")); dataSource.setDriverClassName(env.getProperty("assinante.db.driver-class-name")); return dataSource; } // 专门用于查询凭证的JDBC Template @Bean(name = "assinanteJdbcTemplate") public JdbcTemplate assinanteJdbcTemplate(@Qualifier("assinanteDataSource") DataSource dataSource) { return new JdbcTemplate(dataSource); } // 原有JPA配置保留,供其他业务操作Assinante实体使用 @Bean(name = "assinanteEntityManagerFactory") public LocalContainerEntityManagerFactoryBean assinanteEntityManagerFactory( EntityManagerFactoryBuilder builder, @Qualifier("assinanteDataSource") DataSource dataSource) { return builder .dataSource(dataSource) .packages("com.yourpackage.entity.assinante") .persistenceUnit("assinantePU") .build(); } @Bean(name = "assinanteEntityManager") public EntityManager assinanteEntityManager( @Qualifier("assinanteEntityManagerFactory") EntityManagerFactory entityManagerFactory) { return entityManagerFactory.createEntityManager(); } }
步骤2:用JDBC Template初始化主数据源
修改DataSourceConfig,通过注入的assinanteJdbcTemplate直接查询凭证,不再依赖JPA组件:
@Configuration public class DataSourceConfig { private final JdbcTemplate assinanteJdbcTemplate; private final Environment env; public DataSourceConfig(@Qualifier("assinanteJdbcTemplate") JdbcTemplate assinanteJdbcTemplate, Environment env) { this.assinanteJdbcTemplate = assinanteJdbcTemplate; this.env = env; } @Primary @Bean(name = "primaryDataSource") public DataSource primaryDataSource() { // 从环境变量/配置文件获取当前实例对应的客户标识(比如CNPJ) String clientCnpj = env.getProperty("client.cnpj"); // 直接JDBC查询凭证 String sql = "SELECT db_jdbc_url, db_user, db_password FROM assinante WHERE cnpj_da_base = ?"; Assinante credentials = assinanteJdbcTemplate.queryForObject(sql, new Object[]{clientCnpj}, (rs, rowNum) -> { Assinante assinante = new Assinante(); assinante.setDatabaseURL(rs.getString("db_jdbc_url")); assinante.setDatabaseUser(rs.getString("db_user")); assinante.setDatabasePassword(rs.getString("db_password")); return assinante; }); // 初始化主库HikariCP数据源 HikariDataSource dataSource = new HikariDataSource(); dataSource.setJdbcUrl(credentials.getDatabaseURL()); dataSource.setUsername(credentials.getDatabaseUser()); dataSource.setPassword(credentials.getDatabasePassword()); dataSource.setDriverClassName(env.getProperty("primary.db.driver-class-name")); // 可添加HikariCP连接池配置 dataSource.setMaximumPoolSize(env.getProperty("primary.db.pool.size", Integer.class, 10)); return dataSource; } // 主库JPA配置,依赖上述初始化完成的主数据源 @Primary @Bean(name = "entityManagerFactory") public LocalContainerEntityManagerFactoryBean entityManagerFactory( EntityManagerFactoryBuilder builder, @Qualifier("primaryDataSource") DataSource dataSource) { return builder .dataSource(dataSource) .packages("com.yourpackage.entity.main") .persistenceUnit("primaryPU") .build(); } }
注意事项
- 公共库的连接信息建议通过加密配置(如Jasypt)或配置中心管理,避免硬编码。
- 添加异常处理:若查询不到对应客户的凭证,直接抛出启动异常,避免应用处于无效状态。
- 客户标识(如CNPJ)需在实例启动时确定,可通过环境变量、启动参数或配置文件传入。
内容的提问来源于stack exchange,提问作者Lucas Amaral
相关产品推荐
相关产品推荐

