Spring中同一数据库多连接池如何共享TransactionManager实现事务共享?
解决跨多DataSource(同数据库)的Spring JPA事务问题
问题根源
你配置了多套独立的DataSource、EntityManagerFactoryBean和PlatformTransactionManager,每套组件绑定自己的连接池。Spring的@Transactional默认仅关联单个事务管理器,当BarService的方法要求Propagation.MANDATORY(必须存在当前事务)时,它期望的是自身绑定的事务管理器的事务,但当前存在的是FooService对应事务管理器的事务,两者不匹配,因此触发无事务的报错。
解决方案
由于所有DataSource指向同一个物理数据库,可以通过以下两种方案实现跨连接池的同物理事务:
方案1:使用JTA事务管理器(推荐)
JTA可以统一管理多个DataSource的事务,即使指向同一数据库,也能让所有操作处于同一个物理事务中。
配置步骤:
- 添加JTA依赖(以HikariCP + Atomikos为例):
<!-- Atomikos JTA核心依赖 --> <dependency> <groupId>com.atomikos</groupId> <artifactId>transactions-jta</artifactId> <version>4.0.6</version> </dependency> <dependency> <groupId>com.atomikos</groupId> <artifactId>transactions-hibernate5</artifactId> <version>4.0.6</version> </dependency>
- 配置JTA版DataSource:
@Bean(name = "fooDataSource") public DataSource fooDataSource() { AtomikosDataSourceBean ds = new AtomikosDataSourceBean(); ds.setUniqueResourceName("foo-db"); ds.setXaDataSourceClassName("com.mysql.cj.jdbc.MysqlXADataSource"); Properties props = new Properties(); props.setProperty("url", "jdbc:mysql://localhost:3306/test"); props.setProperty("user", "root"); props.setProperty("password", "password"); ds.setXaProperties(props); ds.setMaxPoolSize(10); return ds; } @Bean(name = "barDataSource") public DataSource barDataSource() { AtomikosDataSourceBean ds = new AtomikosDataSourceBean(); ds.setUniqueResourceName("bar-db"); ds.setXaDataSourceClassName("com.mysql.cj.jdbc.MysqlXADataSource"); Properties props = new Properties(); props.setProperty("url", "jdbc:mysql://localhost:3306/test"); props.setProperty("user", "root"); props.setProperty("password", "password"); ds.setXaProperties(props); ds.setMaxPoolSize(5); return ds; }
- 配置EntityManagerFactory时指定JTA事务类型:
@Bean(name = "fooEntityManagerFactory") public LocalContainerEntityManagerFactoryBean fooEntityManagerFactory( EntityManagerFactoryBuilder builder, @Qualifier("fooDataSource") DataSource dataSource) { return builder .dataSource(dataSource) .packages("com.yourpackage.foo") .persistenceUnit("fooPU") .properties(jpaJtaProperties()) .build(); } @Bean(name = "barEntityManagerFactory") public LocalContainerEntityManagerFactoryBean barEntityManagerFactory( EntityManagerFactoryBuilder builder, @Qualifier("barDataSource") DataSource dataSource) { return builder .dataSource(dataSource) .packages("com.yourpackage.bar") .persistenceUnit("barPU") .properties(jpaJtaProperties()) .build(); } private Map<String, Object> jpaJtaProperties() { Map<String, Object> props = new HashMap<>(); props.put("hibernate.transaction.jta.platform", "com.atomikos.hibernate4.AtomikosPlatform"); props.put("javax.persistence.transactionType", "JTA"); return props; }
- 配置全局JTA事务管理器:
@Bean public PlatformTransactionManager transactionManager() throws SystemException { JtaTransactionManager txManager = new JtaTransactionManager(); txManager.setTransactionManager(atomikosTransactionManager()); txManager.setUserTransaction(atomikosUserTransaction()); return txManager; } @Bean public UserTransaction atomikosUserTransaction() throws SystemException { UserTransactionImp userTransactionImp = new UserTransactionImp(); userTransactionImp.setTransactionTimeout(300); return userTransactionImp; } @Bean(initMethod = "init", destroyMethod = "close") public TransactionManager atomikosTransactionManager() { UserTransactionManager userTransactionManager = new UserTransactionManager(); userTransactionManager.setForceShutdown(false); return userTransactionManager; }
- 修改
@Transactional注解,指定使用JTA事务管理器:
@Service public class FooService { private final FooRepository fooRepository; private final BarService barService; public FooService(FooRepository fooRepository, BarService barService) { this.fooRepository = fooRepository; this.barService = barService; } @Transactional(value = "transactionManager") public void saveFoo(Foo foo) { fooRepository.save(foo); barService.somethingElse(foo); } } @Service public class BarService { private final BarRepository barRepository; public BarService(BarRepository barRepository) { this.barRepository = barRepository; } @Transactional(value = "transactionManager", propagation = Propagation.MANDATORY) public void somethingElse(Foo foo) { barRepository.saveAndFlush(new FoobarEvent(foo)); } }
方案2:手动关联EntityManager到当前事务(轻量方案)
若不想引入JTA依赖,可以在调用跨连接池的Repository前,手动让对应的EntityManager加入当前事务。
步骤:
- 在
BarService中注入对应EntityManager:
@Service public class BarService { @PersistenceContext(unitName = "barPU") private EntityManager barEntityManager; private final BarRepository barRepository; public BarService(BarRepository barRepository) { this.barRepository = barRepository; } @Transactional(propagation = Propagation.MANDATORY, value = "fooTransactionManager") public void somethingElse(Foo foo) { // 手动让barEntityManager加入当前事务 barEntityManager.joinTransaction(); barRepository.saveAndFlush(new FoobarEvent(foo)); } }
注意:
value = "fooTransactionManager"要指定为FooService使用的事务管理器,确保Mandatory检查的是同一事务管理器的事务。由于两个DataSource指向同一数据库,加入事务后JDBC连接会共享同一个物理数据库事务。
关键注意事项
- 确保所有DataSource连接的是同一个数据库实例,使用JTA方案时需数据库支持XA事务。
- 轻量方案需保证事务管理器的一致性,避免出现事务不匹配的情况。
内容的提问来源于stack exchange,提问作者fap
相关产品推荐
相关产品推荐

