// Service A
@Service
public class AService {
@Autowired private BService bService;
@Autowired private ADao aDao;
@Transactional
public void a() {
aDao.updateA();
b(); // 內部呼叫
}
@Transactional
public void b() {
aDao.updateB();
cService.c(); // 跨 Service 呼叫
}
}
// Service C
@Service
public class CService {
@Autowired private CDao cDao;
@Autowired private DService dService;
@Transactional(rollbackFor = MyException.class)
public void c() {
cDao.updateC();
dService.d(); // 拋出 MyException
}
}
// Service D
@Service
public class DService {
@Autowired private DDao dDao;
@Transactional(rollbackFor = MyException.class)
public void d() {
dDao.updateD();
throw new MyException(); // 自訂 SQLException 子類
}
}
期望:d() 拋出 MyException 時,d()、c() 回滾;a()、b() 不回滾。
noRollbackFor 失效@Transactional(noRollbackFor = MyException.class)
public void a() { ... }
結果:四個方法全部回滾。
原因:Spring 在容器啟動時解析所有 @Transactional 方法的 rollbackRules,運行時 TransactionAspectSupport.invokeWithinTransaction 捕獲異常後,直接調用 completeTransactionAfterThrowing 進行全局回滾判斷,noRollbackFor 僅影響當前代理方法的規則,無法阻止外層事務傳播的回滾決策。
PROPAGATION_REQUIRES_NEW 失效@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = MyException.class)
public void c() { ... }
結果:d() 根本未執行。
原因:REQUIRES_NEW 需要透過代理物件呼叫才能開啟新事務。若 b() 直接呼叫 c()(this 呼叫),繞過代理,新事務不生效,仍在外層事務中運行;若 c() 內捕獲異常並吞沒,外層事務不回滾,但 d() 還是執行了——實測發現 d() 未執行,推測為事務管理器檢測到嵌套新事務與現有事務衝突導致異常提前終止。
PROPAGATION_REQUIRED:所有巢狀呼叫共享同一個 Connection、同一個事務。rollbackOnly = true,整個連線的事務必須回滾,無法部分提交。noRollbackFor 僅決定「是否標記 rollbackOnly」,無法改變「共享連線」的物理事實。@Autowired private PlatformTransactionManager txManager;
public void a() {
// 外層不開啟事務,或開啟 REQUIRED
TransactionTemplate template = new TransactionTemplate(txManager);
template.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRES_NEW);
template.execute(status -> {
try {
cService.c();
} catch (MyException e) {
status.setRollbackOnly(); // 僅回滾內層
throw e;
}
return null;
});
// 繼續外層邏輯,不受影響
}
AService、BService 使用一個事務管理器(或無事務)。CService、DService 獨立部署為微服務,或至少獨立資料源 + 獨立事務管理器。d() 的高風險操作(如外部 API 呼叫、不確定計算)前置驗證,失敗拋業務異常而非 SQL 異常。d() 失敗時,記錄失敗日誌,由排程任務異步補償/回滾相關資料,而非依賴資料庫事務回滾。MyException 改為非 SQLException 子類(如繼承 RuntimeException),並精確控制 rollbackFor。@Transactional(rollbackFor = Exception.class),除非明確不需回滾。this.transactionalMethod(),改用 AopContext.currentProxy() 或拆分 Bean。@Transactional("specificTxManager")。