StackTraces
Spring Boot已驗證

Spring Boot 多數據源報 NoUniqueBeanDefinitionException

@debug_master27 天前更新閱讀 6 min00

容器裡有多個 PlatformTransactionManager Bean 時,@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(根因分析)

  1. TransactionAspectSupport 解析機制:
    • Spring 執行事務攔截時,TransactionAspectSupport.determineTransactionManager 會先檢查 @Transactional 的 value 或 transactionManager 屬性。
    • 若屬性未指定,Spring 會根據類型 PlatformTransactionManager.class 從 BeanFactory 查找單一匹配的 Bean。
  2. 多數據源二義性衝突:
    • 當應用引入多數據源並註冊了 orderTransactionManager 與 userTransactionManager 時,若未定義 @Primary,容器記憶體在兩個地位均等的候選 Bean,導致依賴解析失敗。
    • 往往在單數據源重構為多數據源時產生:新加事務管理器後,存量程式碼未做適配,導致原本可運行的缺省方法瞬間全部失效。
  3. 潛在風險:最大的業務隱患在於若誤用了不匹配的事務管理器,可能導致“事務在 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 進行顯式聲明。

References(參考來源)

最後更新於 2026-09-14

這篇幫到你了嗎?

剛解決了一個棘手的報錯?花兩分鐘記錄下來,幫助下一個遇到同樣問題的開發者。

貢獻一條解法