StackTraces

Spring Boot 事務失效:先定段落,再進分支

事務沒起作用有兩種形態:裝配期直接拋例外,或者運行期靜默不回滾。先讀報錯簽名和事務 DEBUG 日誌,判斷自己卡在「事務管理器裝配、代理邊界、傳播行為、回滾判定」哪一段,再按分支裡的判據命令逐條排除成因。

排查全景圖
全景導航 · 4 階段 × 11 條分支,先在圖上定位現象,再進下方路徑

按首個可操作的例外分流

事務要真正生效得過四關:容器裡有一個可用的事務管理器、AOP 代理把方法包住了、傳播行為拿到了正確的物理連接、例外按規則觸發回滾。任何一關沒過,現象都是「事務沒起作用」。先分清你遇到的是裝配期的硬報錯,還是運行期的靜默失效,再按報錯簽名或日誌對號進分支。

11 條路徑 →
01 · 環境

當前環境

先記錄持久層用的是 JDBC/MyBatis 還是 JPA、啟動類有沒有 exclude 過自動設定、項目裡是否自定義了 DataSource Bean。這三件事決定了事務管理器該由誰註冊。

  • 確認依賴裡有沒有 spring-boot-starter-jdbc 或 spring-boot-starter-data-jpa
  • 確認 @SpringBootApplication 的 exclude / excludeName 列表
  • 確認是否有多數據源或公共 DataConfig 之類自定義了 DataSource Bean
02 · 簽名

報錯識別

例外簽名 / SIGNATURE
org.springframework.beans.factory.NoSuchBeanDefinitionException:
  No bean named 'transactionManager' available:
    expected at least 1 bean which qualifies as autowire candidate.
    at org.springframework.transaction.interceptor.TransactionAspectSupport.determineTransactionManager(TransactionAspectSupport.java:507)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:379)
常見變體 / VARIANTS
No qualifying bean of type 'org.springframework.transaction.PlatformTransactionManager' available:
  expected at least 1 bean which qualifies as autowire candidate
  • No bean named 'transactionManager' available → 按預設名字找不到事務管理器,命中本分支;走下方三條判據定因
  • 异常在启动时就抛出 → 不是這個分支——裝配期報錯通常是 DataSource 設定失敗,屬於數據源鏈路
  • expected single matching bean but found 2 → 不是缺失而是二義性,去 A2

爆發點在第一個事務方法調用時,不在啟動階段;例外棧裡一定有 TransactionAspectSupport.determineTransactionManager。

03 · 安全操作

快速解法

三條判據分別對應三種成因:自動裝配被排除、缺依賴、自定義數據源後沒補管理器。按序執行,用輸出鎖定成因。

判據一:查是否排除了事務自動設定。有輸出 → 自動裝配被 exclude,刪掉 exclude 即可

grep -rn "exclude.*TransactionManager\|excludeName.*TransactionManager" src/main/java

判據二:看自動設定報告。DataSourceTransactionManagerAutoConfiguration 落在 NegativeMatches → 缺 spring-boot-starter-jdbc/data-jpa

java -jar app.jar --debug | grep -A3 "DataSourceTransactionManagerAutoConfiguration"

判據三:查自定義 DataSource Bean。自定義了 → 自動設定整體退讓,必須手動補事務管理器

grep -rn "@Bean" -A2 src/main/java | grep -n "DataSource"

修復(自定義數據源或多數據源場景):手動註冊管理器

@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
    return new DataSourceTransactionManager(dataSource);
}
自動設定只在容器裡沒有 DataSource Bean 時才生效。你一旦自己聲明瞭 DataSource,Spring Boot 就整體退讓,事務管理器也得自己補。
04 · 確認

驗證

  1. 啟動後 /actuator/beans 裡能看到 PlatformTransactionManager,或日誌中自動設定報告顯示匹配成功
  2. 調用事務方法不再拋 NoSuchBeanDefinitionException
  3. 管理器名字不叫 transactionManager 時,在 @Transactional(transactionManager = "...") 顯式指定
05 · 繼續診斷

解法無效時

  1. 多數據源場景:每個庫一套 DataSource + TransactionManager,逐個確認是否都註冊齊了
  2. JPA 項目要確認管理器類型是 JpaTransactionManager;錯用 DataSourceTransactionManager 會導致 EntityManager 不參與事務
06 · 相鄰原因

相關

常見問題

啟動報 No bean named 'transactionManager' available 怎麼解決?
容器裡沒有可用的事務管理器,只有三種可能:自動裝配被排除、缺 spring-tx 相關依賴,或者自定義了數據源後沒補管理器。按序執行判據命令核對,用輸出鎖定是哪一種,再對症補上。
多數據源下報 NoUniqueBeanDefinitionException(found 2: orderTransactionManager, userTransactionManager)怎麼辦?
事務管理器有多個,@Transactional 不知道該用哪一個。快速止血是給預設庫的事務管理器標 @Primary,先恢復存量程式碼;終態做法是逐個方法顯式綁定 transactionManager 屬性。
沒有報錯但事務不生效,日誌裡一句 Creating new transaction 都沒有?
典型是同類自調用:調用沒穿過代理,事務攔截器根本沒被觸發。先用日誌坐實事務壓根沒開,再按判據確認調用寫法,最後選改法——拆到另一個 Bean,或注入自身代理。
報 AopConfigException: Could not generate CGLIB subclass 是什麼原因?
類或方法被 final 修飾,CGLIB 生成不了子類代理。先定位 final 在哪一層:類 final 是硬報錯、好找;方法 final 是靜默失效,要靠可見性判據去抓,再按具體情況去掉 final 或改用接口代理。
private 方法上的 @Transactional 不報錯也不生效,怎麼處理?
註解標在了代理夠不到的方法上,private 方法或接口代理下的非 public 方法都屬於這類。先確認可見性判據,再把事務邊界上移到代理能攔到的位置,由外部 public 方法承載事務,而不是把 private 硬改成 public 破壞封裝。
日誌出現 Suspending current transaction, creating new transaction 說明什麼?
內層用了 REQUIRES_NEW,會掛起當前事務並另開一個獨立事務,內層提交後不會跟隨外層回滾。先確認是掛起而非加入,再按業務一致性要求決定改回 REQUIRED,還是保留獨立事務。
報 JpaDialect does not support savepoints 怎麼辦?
NESTED 傳播依賴保存點,而當前事務管理器(例如 HibernateJpaDialect)不支持。先確認管理器類型坐實成因,再按業務一致性要求選:換成 REQUIRES_NEW、換支持保存點的管理器,或者重構掉嵌套需求。
@Async 或子執行緒裡的事務不傳播,怎麼確認?
事務上下文綁定在執行緒上,跨執行緒不會自動傳遞。子執行緒日誌會是 Creating new transaction with name [null],而不是 Participating in existing transaction。先用日誌坐實子執行緒沒加入外層事務,再按是否需要同生共死選方案。
拋了例外事務卻沒回滾,日誌裡 Initiating transaction commit 反而出現在例外之後?
預設只對 RuntimeException 和 Error 回滾,受檢例外例如 IOException 不會觸發。先用判據確認例外譜系,再決定是逐個補 rollbackFor,還是從架構層統一例外策略。
日誌顯示 Completing transaction ... after return: null,而不是 after throwing?
方法內的 try-catch 把例外吞掉了,事務攔截器看到的是正常返回,於是執行提交。先用日誌確認是 after return,再定位吞例外的 catch,最後按業務決定要不要保留降級邏輯。
報 UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only?
內層方法把事務標成了 rollback-only,外層 catch 住例外繼續提交,提交時才發現只能回滾。順著 rollback-only 找到標它的內層方法,再決定是讓整體回滾,還是把內層改成獨立事務。