首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

Spring 事務回滾機制深度解析:@Transactional 失效場景、手動回滾、AutoCommit 陷阱

     編輯:Java 綜合教學

Spring 事務回滾機制深度解析:@Transactional 失效場景、手動回滾、AutoCommit 陷阱

事務管理是企業級開發核心。本文從原理層面剖析 Spring 宣告式事務 (@Transactional) 回滾規則,重點講解 自動提交 (AutoCommit) 與異常捕獲導致回滾失效 的根因,並提供手動回滾、配置優化等最佳實踐方案。

一、問題現場:多筆資料庫操作部分失敗

@Service
public class OrderService {
    @Autowired OrderDao orderDao;
    @Autowired AccountDao accountDao;

    public void placeOrder(Order order) {
        orderDao.save(order);           // 操作 1 成功
        accountDao.deductBalance(order.getUserId(), order.getAmount()); // 操作 2 拋異常
        orderDao.updateStatus(order.getId(), "PAID"); // 操作 3 不會執行
    }
}

期望:操作 1 回滾,資料庫無變更。
實際:若配置不當,操作 1 已提交,造成資料不一致。

二、異常體系與回滾規則核心

異常類型繼承鏈預設回滾說明
RuntimeExceptionException → RuntimeException✅ 是未檢查異常,編譯器不強制捕獲
ErrorThrowable → Error✅ 是JVM 嚴重錯誤 (OOM, StackOverflow),通常不捕獲
受檢異常 (如 IOException, SQLException)Exception → 非 RuntimeException❌ 否編譯器強制 try-catch 或 throws,需額外配置

結論:@Transactional 預設僅對 RuntimeException 及 Error 回滾。受檢異常需加 rollbackFor = Exception.class。

三、AutoCommit 陷阱:回滾失效的隱形殺手

現象重現

@Transactional(rollbackFor = Exception.class)
public void test() {
    try {
        doDbStuff1();  // INSERT
        doDbStuff2();  // 拋 SQLException
    } catch (Exception e) {
        e.printStackTrace(); // 吞掉異常!
    }
}

結果:doDbStuff1 的 INSERT **已提交**,未回滾。

根因分析

  1. 資料源 (如 Druid/HikariCP) 預設 defaultAutoCommit = true。
  2. Spring 事務管理器啟動時會 暫時關閉 AutoCommit (connection.setAutoCommit(false)),由 Spring 控制提交/回滾。
  3. 但:若你在方法內 捕獲並吞掉異常 (不重拋),Spring 的 TransactionInterceptor 感知不到異常,以為方法正常結束 → 提交事務。
  4. 此時連線已還原 autoCommit=true,操作 1 實際已落庫。

驗證 Druid 預設值

// DruidDataSource 片段
public class DruidDataSource {
    private boolean defaultAutoCommit = true; // 預設 true
    // ...
}

四、解決方案對比

方案代碼示例優點缺點
不捕獲異常,向上拋出 (推薦)
@Transactional(rollbackFor = Exception.class)
public void test() throws Exception {
    doDbStuff1();
    doDbStuff2(); // 異常自動拋出
}
最簡潔,Spring 自動回滾上層需處理異常
捕獲後手動標記回滾
@Transactional(rollbackFor = Exception.class)
public void test() {
    try {
        doDbStuff1();
        doDbStuff2();
    } catch (Exception e) {
        log.error("業務失敗", e);
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); // 關鍵
    }
}
可自定義錯誤處理/日誌,不拋出侵入業務代碼,易忘
配置 DataSource AutoCommit=false
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <property name="defaultAutoCommit" value="false"/>
</bean>
從根源關閉自動提交影響全局,需確保所有路徑受事務管理
使用 TransactionTemplate 編程式事務
transactionTemplate.execute(status -> {
    try { doDbStuff1(); doDbStuff2(); }
    catch (Exception e) { status.setRollbackOnly(); throw e; }
});
精確控制事務邊界樣板代碼多

五、其他常見 @Transactional 失效場景

  1. 方法非 public:@Transactional 僅對 public 方法生效 (CGLib 代理除外,但不建議)。
  2. 內部調用 (this.xxx()):繞過代理,需注入自己 (@Autowired SelfService self) 或用 AopContext.currentProxy()。
  3. 異常被全局捕獲 (ControllerAdvice) 未重拋:同吞掉異常,事務提交。
  4. 資料庫引擎不支援事務:MyISAM、MongoDB (單文檔事務 4.0+ 支援)、Redis (Lua 腳本原子性)。
  5. 傳播行為錯誤:PROPAGATION_NEVER、NOT_SUPPORTED 掛起事務。
  6. 未掃描到 @EnableTransactionManagement:配置類缺少註解。

六、最佳實踐總結

  1. 統一加 rollbackFor = Exception.class:覆蓋受檢異常,避免遺漏。
  2. 業務層不捕獲異常:讓異常自然拋到全局異常處理器 (@RestControllerAdvice) 統一返回錯誤碼,Spring 自動回滾。
  3. 必須捕獲時:TransactionAspectSupport.currentTransactionStatus().setRollbackOnly() + 重拋或轉換為業務異常。
  4. DataSource 設 defaultAutoCommit=false:作為防線,但不替代正確異常處理。
  5. 測試驗證:單元測試用 @Rollback(false) 觀察資料庫狀態,或用 TransactionSynchronizationManager.isActualTransactionActive() 斷言事務狀態。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved