事務管理是企業級開發核心。本文從原理層面剖析 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 已提交,造成資料不一致。
| 異常類型 | 繼承鏈 | 預設回滾 | 說明 |
|---|---|---|---|
RuntimeException | Exception → RuntimeException | ✅ 是 | 未檢查異常,編譯器不強制捕獲 |
Error | Throwable → Error | ✅ 是 | JVM 嚴重錯誤 (OOM, StackOverflow),通常不捕獲 |
受檢異常 (如 IOException, SQLException) | Exception → 非 RuntimeException | ❌ 否 | 編譯器強制 try-catch 或 throws,需額外配置 |
結論:@Transactional 預設僅對 RuntimeException 及 Error 回滾。受檢異常需加 rollbackFor = Exception.class。
@Transactional(rollbackFor = Exception.class)
public void test() {
try {
doDbStuff1(); // INSERT
doDbStuff2(); // 拋 SQLException
} catch (Exception e) {
e.printStackTrace(); // 吞掉異常!
}
}
結果:doDbStuff1 的 INSERT **已提交**,未回滾。
defaultAutoCommit = true。connection.setAutoCommit(false)),由 Spring 控制提交/回滾。TransactionInterceptor 感知不到異常,以為方法正常結束 → 提交事務。autoCommit=true,操作 1 實際已落庫。// 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 僅對 public 方法生效 (CGLib 代理除外,但不建議)。@Autowired SelfService self) 或用 AopContext.currentProxy()。PROPAGATION_NEVER、NOT_SUPPORTED 掛起事務。rollbackFor = Exception.class:覆蓋受檢異常,避免遺漏。@RestControllerAdvice) 統一返回錯誤碼,Spring 自動回滾。TransactionAspectSupport.currentTransactionStatus().setRollbackOnly() + 重拋或轉換為業務異常。defaultAutoCommit=false:作為防線,但不替代正確異常處理。@Rollback(false) 觀察資料庫狀態,或用 TransactionSynchronizationManager.isActualTransactionActive() 斷言事務狀態。