????????在 Java 開發中,事務是保證數據一致性和完整性的關鍵機制,尤其在涉及多步數據庫操作的業務場景中不可或缺。然而,在實際開發過程中,事務常常會出現 “失效” 的情況 —— 預期的回滾沒有發生,數據出現不一致。
????????Java 事務(尤其是基于 Spring 的聲明式事務)的正常運作依賴于一系列底層機制和約定,當這些機制被破壞或約定未被遵守時,就會導致事務失效。核心原因可歸納為以下幾類:?
(1)事務傳播行為配置不當:事務傳播行為決定了多個事務方法相互調用時的執行規則,若配置錯誤(如使用PROPAGATION_SUPPORTS或PROPAGATION_NOT_SUPPORTED),會導致事務無法按預期生效。?
(2)異常處理不當:事務默認只對未捕獲的RuntimeException及其子類異常回滾,若異常被手動捕獲且未重新拋出,或拋出的是受檢異常,會導致事務不回滾。?
(3)方法訪問權限問題:Spring 事務基于 AOP 動態代理實現,若目標方法是private、final或static修飾的,代理無法生效,事務自然失效。?
(4)數據源未配置事務管理器:事務的管理依賴于事務管理器,若未為數據源正確配置PlatformTransactionManager,事務注解將無法發揮作用。?
(5)自調用問題:在同一個類中,一個非事務方法調用本類的事務方法時,由于未經過代理對象,事務會失效。?
? ? ? ? 通常有以下失效的場景:
????????場景一:事務傳播行為配置錯誤?
????????場景描述:當一個事務方法調用另一個事務方法時,若傳播行為設置不合理,可能導致事務無法正常回滾。例如,在嵌套業務中使用PROPAGATION_REQUIRES_NEW時,內層事務提交后,外層事務回滾不會影響內層;若使用PROPAGATION_SUPPORTS,則方法會跟隨當前事務(若不存在則不開啟事務)。?
代碼示例(錯誤):?
?
@Service
public class OrderService {@Autowiredprivate PaymentService paymentService;@Transactional(propagation = Propagation.REQUIRED)public void createOrder() {// 保存訂單saveOrder();// 調用支付方法,傳播行為為SUPPORTSpaymentService.makePayment();}
}@Service
public class PaymentService {@Transactional(propagation = Propagation.SUPPORTS)public void makePayment() {// 支付邏輯,若當前無事務則不開啟事務updatePaymentStatus();// 模擬異常int i = 1 / 0;}
}
????????問題分析:makePayment方法的傳播行為為SUPPORTS,若createOrder的事務未生效(或傳播行為不匹配),則支付操作不會在事務中執行,異常發生后無法回滾。?
????????解決方法:根據業務需求選擇合適的傳播行為,如核心業務使用REQUIRED(默認值),確保方法在事務中執行。?
????????場景二:異常被捕獲但未重新拋出?
????????場景描述:事務方法中若手動捕獲了異常且未重新拋出,Spring 會認為事務執行成功,不會觸發回滾。?
代碼示例(錯誤):?
?
@Service
public class UserService {@Autowiredprivate UserMapper userMapper;@Transactionalpublic void updateUserInfo() {try {userMapper.updateName(1, "newName");// 模擬異常int i = 1 / 0;} catch (Exception e) {// 僅捕獲異常未處理log.error("更新失敗", e);}}
}
????????問題分析:異常被try-catch捕獲后,未拋出到外層,Spring 無法感知異常,因此不會回滾updateName操作。?
????????解決方法:捕獲異常后重新拋出,或通過@Transactional(rollbackFor = Exception.class)指定回滾異常類型,并在外層處理。?
????????場景三:方法訪問權限問題?
????????場景描述:Spring 事務基于 AOP 動態代理實現,若事務方法被private、final或static修飾,代理無法重寫該方法,導致事務注解失效。?
代碼示例(錯誤):?
@Service
public class ProductService {@Autowiredprivate ProductMapper productMapper;// private修飾的事務方法@Transactionalprivate void reduceStock(Long productId, int quantity) {productMapper.decreaseStock(productId, quantity);}public void processOrder(Long productId, int quantity) {reduceStock(productId, quantity); // 調用私有方法}
}
?????????問題分析:reduceStock為私有方法,Spring 無法生成代理方法,@Transactional注解失效,庫存減少操作在異常發生時不會回滾。?
????????解決方法:將事務方法修改為public修飾,且避免使用final或static。?
????????場景四:未配置事務管理器?
????????場景描述:Spring 事務的生效依賴于事務管理器(如DataSourceTransactionManager),若未在配置類中聲明事務管理器,@Transactional注解將不起作用。?
????????代碼示例(錯誤配置):?
@Configuration
@MapperScan("com.example.mapper")
public class MyBatisConfig {@Beanpublic DataSource dataSource() {// 配置數據源HikariConfig config = new HikariConfig();config.setJdbcUrl("jdbc:mysql://localhost:3306/test");return new HikariDataSource(config);}
}
????????問題分析:僅配置了數據源,未配置PlatformTransactionManager,Spring 無法管理事務。?
????????解決方法:添加事務管理器配置:?
????????場景五:同一類內方法自調用?
????????場景描述:在同一個類中,非事務方法直接調用本類的事務方法時,由于調用的是原始對象(非代理對象),事務會失效。?
????????代碼示例(錯誤):?
@Service
public class OrderService {@Autowiredprivate OrderMapper orderMapper;public void createOrder() {// 非事務方法調用本類事務方法saveOrder();}@Transactionalpublic void saveOrder() {orderMapper.insert(new Order());int i = 1 / 0; // 模擬異常}
}
問題分析:createOrder未被@Transactional修飾,調用saveOrder時使用的是當前類實例(非代理對象),事務注解失效,異常發生后訂單數據仍會被插入。?
解決方法:?
(1)將事務方法拆分到另一個服務類中,通過依賴注入調用(推薦)。?
(2)自注入代理對象(需開啟exposeProxy = true):?
?
? ? ? ? 在這里,給出幾個關于事務失效的排查建議?
(1)檢查事務注解:確保方法被@Transactional修飾,且注解屬性(如rollbackFor、propagation)配置正確。?
(2)驗證代理模式:Spring 默認使用 JDK 動態代理(接口),若使用 CGLIB 代理需確保類未被final修飾,且依賴中包含 CGLIB 庫。?
(3)查看異常處理:檢查事務方法中是否有異常被捕獲后未重新拋出,確保異常類型符合rollbackFor配置。?
(4)調試事務日志:在application.properties中添加日志配置logging.level.org.springframework.transaction=DEBUG,觀察事務的開啟、提交和回滾日志。?