容器里有多个
PlatformTransactionManagerBean 时,@Transactional缺省按类型查找,直接抛NoUniqueBeanDefinitionException: expected single matching bean but found 2。解法只有两条路:标@Primary定默认,或@Transactional(transactionManager = "...")逐个显式指定。
Issue & Env(案发现场与环境)
- JDK 版本:OpenJDK 17 / 21
- Spring Boot 版本:2.7.x / 3.x
- 架构背景:系统从单数据源演进为多数据源架构(如主从分离、业务分库或读写分离)。
- 触发背景:系统完成了多数据源配置,应用正常启动。然而在调用未显式指定
transactionManager属性的@Transactional业务方法时,立即抛出异常中断。
多数据源项目通常会注册多个 DataSource 和配套的事务管理器:
@Configuration
public class DataSourceConfig {
@Bean
public DataSource orderDataSource() { /* ... */ }
@Bean
public PlatformTransactionManager orderTransactionManager(
@Qualifier("orderDataSource") DataSource ds) {
return new DataSourceTransactionManager(ds);
}
@Bean
public DataSource userDataSource() { /* ... */ }
@Bean
public PlatformTransactionManager userTransactionManager(
@Qualifier("userDataSource") DataSource ds) {
return new DataSourceTransactionManager(ds);
}
}
StackTrace(堆栈日志)
org.springframework.beans.factory.NoUniqueBeanDefinitionException:
No qualifying bean of type 'org.springframework.transaction.PlatformTransactionManager' available:
expected single matching bean but found 2: orderTransactionManager, userTransactionManager
at org.springframework.beans.factory.config.DependencyDescriptor.resolveNotUnique(DependencyDescriptor.java:175)
at org.springframework.transaction.interceptor.TransactionAspectSupport.determineTransactionManager(TransactionAspectSupport.java:512)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:379)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
特征同“事务管理器缺失”类似:错误不在启动期爆发,而在第一个事务方法调用时炸。区别在于报错明确指出匹配到多个候选 Bean(found 2)。
Reproduce(复现代码)
在配置了多个 PlatformTransactionManager 的环境下,存在一个缺省配置的 @Transactional 注解方法:
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
// 复现点:在多事务管理器场景下,缺省未指定 transactionManager
@Transactional
public void createOrder(Order o) {
orderMapper.insert(o);
}
}
Root Cause(根因分析)
- TransactionAspectSupport 解析机制:
- Spring 执行事务拦截时,
TransactionAspectSupport.determineTransactionManager会先检查@Transactional的value或transactionManager属性。 - 若属性未指定,Spring 会根据类型
PlatformTransactionManager.class从BeanFactory查找单一匹配的 Bean。
- Spring 执行事务拦截时,
- 多数据源二义性冲突:
- 当应用引入多数据源并注册了
orderTransactionManager与userTransactionManager时,若未定义@Primary,容器内存在两个地位均等的候选 Bean,导致依赖解析失败。 - 往往在单数据源重构为多数据源时产生:新加事务管理器后,存量代码未做适配,导致原本可运行的缺省方法瞬间全部失效。
- 当应用引入多数据源并注册了
- 潜在风险:最大的业务隐患在于若误用了不匹配的事务管理器,可能导致“事务在 A 库开启并提交,而 SQL 执行打在 B 库”,造成长期的静默数据不一致。
可使用以下命令快速扫描工程中缺省事务管理器的调用点:
grep -rn "@Transactional" src/main/java | grep -v "transactionManager"
The Fix(解决方案)
方案 A(推荐):每个事务方法显式指定事务管理器
明确指定绑定的事务管理器名称,使业务语义与物理库精准对应:
@Transactional(transactionManager = "orderTransactionManager")
public void createOrder(Order o) {
orderMapper.insert(o);
}
@Transactional(transactionManager = "userTransactionManager")
public void updateUser(User u) {
userMapper.update(u);
}
方案 B(过渡止血):标记 @Primary 指定默认事务管理器
为主要的业务数据库事务管理器添加 @Primary,作为兜底缺省项:
@Bean
@Primary
public PlatformTransactionManager orderTransactionManager(@Qualifier("orderDataSource") DataSource ds) {
return new DataSourceTransactionManager(ds);
}
注意:方案 B 能快速恢复存量代码运行,但无法解决跨库分布式一致性问题。生产环境推荐逐步迁移至方案 A 进行显式声明。
