容器裡有多個
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 進行顯式聲明。
