Spring 聲明式事務管理是企業級開發基石,而傳播行為決定了方法調用鏈中事務邊界如何延伸、隔離、嵌套。誤用傳播屬性是數據不一致、死鎖、性能劣化的高發源頭。本文系統梳理 7 種傳播類型,結合調用鏈場景與事務邊界圖解,給出選型決策表與避坑指南。
public interface TransactionDefinition {
int getPropagationBehavior(); // 傳播行為
int getIsolationLevel(); // 隔離級別
int getTimeout(); // 超時時間(秒)
boolean isReadOnly(); // 唯讀優化提示
}
隔離級別(ISOLATION_*)遵循 JDBC 標準:DEFAULT、READ_UNCOMMITTED、READ_COMMITTED、REPEATABLE_READ、SERIALIZABLE。本文聚焦傳播行為。
| 常量 | 語義 | 典型場景 | 事務關係 |
|---|---|---|---|
REQUIRED | 支持當前事務,無則新建 | 90% 業務方法、Service 入口 | 同一事務 |
SUPPORTS | 支持當前事務,無則非事務運行 | 只讀查詢、報表導出 | 同一事務或無事務 |
MANDATORY | 必須在現有事務中運行,否則拋異常 | 核心領域邏輯、禁止獨立調用的內部方法 | 同一事務(強制) |
REQUIRES_NEW | **掛起當前事務,強制新建獨立事務** | 獨立審計日誌、短信發送、外部 API 調用、需獨立提交的子業務 | 父子獨立事務 |
NOT_SUPPORTED | 掛起當前事務,以非事務方式運行 | 長耗時非關鍵操作(如大數據導出)、避免長事務持有鎖 | 無事務(父掛起) |
NEVER | 禁止事務,若存在則拋異常 | 嚴禁事務上下文的工具方法、純內存計算 | 無事務(強制) |
NESTED | **在當前事務內創建保存點**,子失敗回滾到保存點,父決定最終提交/回滾 | 多階段業務嘗試、可選分支補償、批量處理中單條失敗不影響整體 | 嵌套事務(依賴父) |
假設調用鏈:ServiceA.methodA() → ServiceB.methodB()
// A: REQUIRED, B: REQUIRED
@Transactional(propagation = REQUIRED)
void methodA() {
methodB(); // 同一事務
// A 後續邏輯拋異常 → 整體回滾(含 B 已執行部分)
}
特點:物理事務唯一,任何一方回滾標記 rollbackOnly=true,最終統一回滾。即使 B 內部未拋異常,A 後續失敗也會導致 B 回滾。
// A: REQUIRED, B: REQUIRES_NEW
@Transactional(propagation = REQUIRED)
void methodA() {
methodB(); // B 獨立事務,A 掛起
// B 已提交,A 後續失敗 → A 回滾,B **不受影響** 已持久化
}
關鍵差異:兩個物理事務。B 提交後釋放鎖,A 回滾不影響 B。適用於「必須持久化」的副作用操作(日誌、通知、計費)。
// A: REQUIRED, B: NESTED
@Transactional(propagation = REQUIRED)
void methodA() {
try {
methodB(); // 設置 Savepoint
} catch (Exception e) {
methodC(); // 嘗試替代分支
}
}
機制:JDBC 3.0 Savepoint。B 回滾僅撤銷 B 的操作,A 可捕獲異常繼續執行 C,**最終由 A 決定整體提交或回滾**。不支持跨數據源,需底層驅動支持 Savepoint(Oracle/PostgreSQL/MySQL InnoDB 支持,SQL Server 部分支持)。
// A: REQUIRED
void methodA() {
methodB_SUPPORTS(); // 同事務
methodB_MANDATORY(); // 同事務,若 A 無事務則拋 IllegalTransactionStateException
methodB_NOT_SUPPORTED(); // A 掛起,B 無事務運行
methodB_NEVER(); // 拋異常(因 A 有事務)
}
REQUIRES_NEWNESTED (確認 DB 支持)REQUIRED (默認)NEVERNOT_SUPPORTEDSUPPORTS (只讀查詢首選)MANDATORY(防御性編程)| 坑點 | 現象 | 根因 | 對策 |
|---|---|---|---|
| 同類內部調用失效 | this.methodB() 傳播屬性不生效 | Spring AOP 基於代理,繞過代理調用 | 注入自身 @Autowired SelfService self 或拆分類 |
| REQUIRES_NEW 導致死鎖 | 父子事務競爭同一行鎖 | 父事務持有鎖未釋放,子事務等待 | 縮小父事務範圍、調整隔離級別、避免長事務 |
| NESTED 不生效 | 子事務拋異常導致父全量回滾 | 數據庫/驅動不支持 Savepoint | 改用 REQUIRES_NEW + 補償邏輯 或 程式化事務 |
| 只讀事務未生效 | readOnly=true 但仍加寫鎖 | MySQL 需開啟 readOnly=true + READ_COMMITTED 才觸發優化 | 設置隔離級別、使用路由到只讀庫 |
| 異常吞沒導致不回滾 | catch 後未重拋,事務提交 | 默認僅回滾 RuntimeException/Error | @Transactional(rollbackFor = Exception.class) 或手動 setRollbackOnly() |
複雜邊界控制(如動態決定是否新事務、多數據源協調)可使用 TransactionTemplate 或 PlatformTransactionManager:
@Autowired PlatformTransactionManager txManager;
public void complexFlow() {
TransactionTemplate tx = new TransactionTemplate(txManager);
tx.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
tx.execute(status -> {
// 獨立事務邏輯
if (needRollback) status.setRollbackOnly();
return result;
});
}
事務傳播本質是事務邊界的協議。REQUIRED 滿足多數場景,REQUIRES_NEW 解決獨立持久化需求,NESTED 提供細粒度回滾彈性(受限於 DB 支持),其餘類型用於邊界防護與性能優化。原則:事務邊界盡可能小、持有時間盡可能短、傳播語義顯式文檔化、異常處理策略統一聲明。掌握這 7 種傳播類型的物理行為差異,是從「會用事務」進階到「駕馭事務」的關鍵門檻。