Spring中事務(wù)失效的8種常見(jiàn)使用場(chǎng)景避坑指南
Spring事務(wù)管理是企業(yè)級(jí)Java應(yīng)用的核心功能,看似簡(jiǎn)單的@Transactional
注解,如果使用不當(dāng)將會(huì)引發(fā)嚴(yán)重的生產(chǎn)問(wèn)題,比如因事務(wù)失效帶來(lái)的數(shù)據(jù)不一致問(wèn)題。
事務(wù)失效往往不會(huì)拋出異常,而是靜默發(fā)生,等到業(yè)務(wù)出現(xiàn)問(wèn)題時(shí)才被發(fā)現(xiàn),造成嚴(yán)重的數(shù)據(jù)不一致。
本文將分析8種導(dǎo)致Spring事務(wù)失效的使用問(wèn)題并提供相應(yīng)的解決方案。
一、事務(wù)注解應(yīng)用在非public方法上
問(wèn)題現(xiàn)象
開(kāi)發(fā)者在非public方法上添加@Transactional
注解,但事務(wù)沒(méi)有生效。
@Service public class UserService { @Autowired private UserMapper userMapper; // 事務(wù)無(wú)效!方法不是public的 @Transactional protected void createUser(User user) { userMapper.insert(user); // 如果這里拋出異常,數(shù)據(jù)不會(huì)回滾 } }
原理分析
Spring AOP的代理機(jī)制默認(rèn)只攔截public方法。這是因?yàn)槭聞?wù)通知是基于Spring AOP實(shí)現(xiàn)的,而Spring AOP默認(rèn)只攔截public方法調(diào)用。
查看AbstractFallbackTransactionAttributeSource
類的源碼可以發(fā)現(xiàn):
// AbstractFallbackTransactionAttributeSource.java protected TransactionAttribute computeTransactionAttribute(Method method, Class<?> targetClass) { // Don't allow no-public methods as required. if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) { return null; } // ... }
非public方法上的@Transactional
注解會(huì)被直接忽略,返回null,導(dǎo)致事務(wù)不生效。
解決方案
確保標(biāo)注@Transactional
的方法為public:
@Service public class UserService { @Autowired private UserMapper userMapper; // 正確:方法是public的 @Transactional public void createUser(User user) { userMapper.insert(user); // 如果拋出異常,數(shù)據(jù)會(huì)回滾 } }
二、同一個(gè)類中方法調(diào)用
問(wèn)題現(xiàn)象
在同一個(gè)類中,非事務(wù)方法調(diào)用事務(wù)方法,或者事務(wù)方法調(diào)用另一個(gè)具有不同事務(wù)屬性的方法,事務(wù)會(huì)失效。
@Service public class OrderService { public void createOrder(Order order) { // 直接調(diào)用同類中的事務(wù)方法 this.createOrderWithTransaction(order); // 如果createOrderWithTransaction方法拋出異常 // 事務(wù)不會(huì)回滾! } @Transactional public void createOrderWithTransaction(Order order) { // 數(shù)據(jù)庫(kù)操作... throw new RuntimeException("故意拋出異常"); } }
原理分析
Spring事務(wù)基于動(dòng)態(tài)代理實(shí)現(xiàn),當(dāng)在同一個(gè)類中調(diào)用方法時(shí),是通過(guò)this引用調(diào)用的,而不是通過(guò)代理對(duì)象。這導(dǎo)致事務(wù)切面無(wú)法攔截方法調(diào)用,事務(wù)自然就失效了。
簡(jiǎn)言之,只有通過(guò)代理對(duì)象調(diào)用方法時(shí),Spring事務(wù)才會(huì)生效。
解決方案
有幾種方法可以解決:
方法1:使用自我注入
@Service public class OrderService { @Autowired private OrderService self; // 自我注入,注入的是代理對(duì)象 public void createOrder(Order order) { // 通過(guò)代理對(duì)象調(diào)用事務(wù)方法 self.createOrderWithTransaction(order); // 如果createOrderWithTransaction方法拋出異常 // 事務(wù)會(huì)正?;貪L } @Transactional public void createOrderWithTransaction(Order order) { // 數(shù)據(jù)庫(kù)操作... throw new RuntimeException("故意拋出異常"); } }
方法2:使用AopContext獲取代理對(duì)象(需要額外配置)
@EnableAspectJAutoProxy(exposeProxy = true) // 配置類上添加此注解 public class AppConfig { // ... } @Service public class OrderService { public void createOrder(Order order) { // 通過(guò)AopContext獲取代理對(duì)象 ((OrderService) AopContext.currentProxy()) .createOrderWithTransaction(order); // 事務(wù)會(huì)正常工作 } @Transactional public void createOrderWithTransaction(Order order) { // 數(shù)據(jù)庫(kù)操作... throw new RuntimeException("故意拋出異常"); } }
方法3:將方法拆分到不同的類中
@Service public class OrderFacadeService { @Autowired private TransactionalOrderService orderService; public void createOrder(Order order) { orderService.createOrderWithTransaction(order); // 事務(wù)會(huì)正常工作 } } @Service public class TransactionalOrderService { @Transactional public void createOrderWithTransaction(Order order) { // 數(shù)據(jù)庫(kù)操作... throw new RuntimeException("故意拋出異常"); } }
三、異常被吞沒(méi)
問(wèn)題現(xiàn)象
開(kāi)發(fā)者在事務(wù)方法中捕獲了異常但沒(méi)有重新拋出,導(dǎo)致事務(wù)無(wú)法回滾。
@Service public class PaymentService { @Autowired private PaymentMapper paymentMapper; @Transactional public void processPayment(Payment payment) { try { paymentMapper.insert(payment); // 其他業(yè)務(wù)邏輯 throw new RuntimeException("支付處理失敗"); } catch (Exception e) { log.error("支付異常", e); // 錯(cuò)誤:捕獲異常但未重新拋出,事務(wù)不會(huì)回滾 } } }
原理分析
Spring事務(wù)管理器是通過(guò)異常觸發(fā)回滾的。當(dāng)方法執(zhí)行過(guò)程中拋出異常,并且這個(gè)異常滿足回滾條件時(shí),事務(wù)管理器才會(huì)執(zhí)行回滾操作。如果異常被捕獲且沒(méi)有重新拋出,Spring事務(wù)管理器就不知道發(fā)生了異常,自然不會(huì)回滾事務(wù)。
解決方案
有兩種解決方法:
方法1:重新拋出異常
@Service public class PaymentService { @Autowired private PaymentMapper paymentMapper; @Transactional public void processPayment(Payment payment) { try { paymentMapper.insert(payment); // 其他業(yè)務(wù)邏輯 throw new RuntimeException("支付處理失敗"); } catch (Exception e) { log.error("支付異常", e); // 正確:重新拋出異常,事務(wù)會(huì)回滾 throw e; // 或者拋出新的異常 } } }
方法2:使用TransactionAspectSupport手動(dòng)回滾
@Service public class PaymentService { @Autowired private PaymentMapper paymentMapper; @Transactional public void processPayment(Payment payment) { try { paymentMapper.insert(payment); // 其他業(yè)務(wù)邏輯 throw new RuntimeException("支付處理失敗"); } catch (Exception e) { log.error("支付異常", e); // 正確:手動(dòng)標(biāo)記事務(wù)回滾 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); } } }
四、異常類型不匹配回滾規(guī)則
問(wèn)題現(xiàn)象
開(kāi)發(fā)者拋出了異常,但事務(wù)沒(méi)有回滾。這通常是因?yàn)閽伋龅漠惓n愋筒粷M足默認(rèn)的回滾規(guī)則。
@Service public class ReportService { @Autowired private ReportMapper reportMapper; @Transactional public void generateReport() { reportMapper.insertReportData(); // 其他業(yè)務(wù)邏輯 if (somethingWrong()) { // 拋出受檢異常,默認(rèn)不會(huì)導(dǎo)致事務(wù)回滾 throw new IOException("報(bào)告生成失敗"); } } }
原理分析
Spring默認(rèn)只在遇到未檢查異常(RuntimeException及其子類)和Error時(shí)才回滾事務(wù)。受檢異常(如IOException、SQLException)被認(rèn)為是業(yè)務(wù)異常,默認(rèn)情況下不會(huì)觸發(fā)事務(wù)回滾。
這是因?yàn)?code>@Transactional注解的默認(rèn)配置是:
@Transactional(rollbackFor = RuntimeException.class)
解決方案
有兩種解決方法:
方法1:指定回滾的異常類型
@Service public class ReportService { @Autowired private ReportMapper reportMapper; // 正確:指定回滾的異常類型包括IOException @Transactional(rollbackFor = {IOException.class}) public void generateReport() throws IOException { reportMapper.insertReportData(); // 其他業(yè)務(wù)邏輯 if (somethingWrong()) { throw new IOException("報(bào)告生成失敗"); // 現(xiàn)在會(huì)觸發(fā)事務(wù)回滾 } } }
方法2:將受檢異常轉(zhuǎn)換為非受檢異常
@Service public class ReportService { @Autowired private ReportMapper reportMapper; @Transactional public void generateReport() { reportMapper.insertReportData(); // 其他業(yè)務(wù)邏輯 try { if (somethingWrong()) { throw new IOException("報(bào)告生成失敗"); } } catch (IOException e) { // 將受檢異常轉(zhuǎn)換為非受檢異常 throw new RuntimeException("報(bào)告生成失敗", e); // 現(xiàn)在會(huì)觸發(fā)事務(wù)回滾 } } }
五、數(shù)據(jù)庫(kù)不支持事務(wù)
問(wèn)題現(xiàn)象
所有事務(wù)配置看起來(lái)都正確,但事務(wù)仍然不生效。
原理分析
某些數(shù)據(jù)庫(kù)引擎不支持事務(wù)功能。最常見(jiàn)的例子是MySQL的MyISAM引擎,它不支持事務(wù)操作。如果表使用的是MyISAM引擎,即使Spring事務(wù)配置正確,也無(wú)法實(shí)現(xiàn)事務(wù)回滾。
解決方案
確保使用支持事務(wù)的數(shù)據(jù)庫(kù)引擎:
1. 對(duì)于MySQL,使用InnoDB引擎而不是MyISAM
2. 檢查表的創(chuàng)建語(yǔ)句,確保引擎類型正確:
-- 檢查表引擎 SHOW TABLE STATUS WHERE Name = 'your_table_name'; -- 修改表引擎為InnoDB ALTER TABLE your_table_name ENGINE=InnoDB;
六、錯(cuò)誤的傳播行為設(shè)置
問(wèn)題現(xiàn)象
在嵌套事務(wù)場(chǎng)景中,內(nèi)部事務(wù)的回滾沒(méi)有按照預(yù)期工作。
@Service public class OrderService { @Autowired private PaymentService paymentService; @Transactional public void createOrder(Order order) { // 保存訂單 orderMapper.insert(order); try { // 調(diào)用支付服務(wù) paymentService.processPayment(order.getPayment()); } catch (Exception e) { log.error("支付失敗,但訂單已創(chuàng)建", e); // 處理支付失敗邏輯,但期望訂單依然保存 } } } @Service public class PaymentService { // 錯(cuò)誤的傳播行為,會(huì)影響外部事務(wù) @Transactional(propagation = Propagation.REQUIRED) public void processPayment(Payment payment) { paymentMapper.insert(payment); throw new RuntimeException("支付處理失敗"); } }
原理分析
Spring提供了不同的事務(wù)傳播行為,用于控制事務(wù)的邊界。最常用的是:
REQUIRED
:默認(rèn)值,如果當(dāng)前存在事務(wù),則加入該事務(wù);如果不存在,則創(chuàng)建新事務(wù)REQUIRES_NEW
:創(chuàng)建新事務(wù),如果當(dāng)前存在事務(wù),則掛起當(dāng)前事務(wù)NESTED
:如果當(dāng)前存在事務(wù),則創(chuàng)建嵌套事務(wù);如果不存在,則等同于REQUIREDSUPPORTS
:如果當(dāng)前存在事務(wù),則加入該事務(wù);如果不存在,則以非事務(wù)方式執(zhí)行
使用不當(dāng)?shù)膫鞑バ袨闀?huì)導(dǎo)致事務(wù)范圍不符合預(yù)期,特別是在異常處理場(chǎng)景中。
解決方案
根據(jù)業(yè)務(wù)需求選擇正確的傳播行為:
@Service public class OrderService { @Autowired private PaymentService paymentService; @Transactional public void createOrder(Order order) { // 保存訂單 orderMapper.insert(order); try { // 調(diào)用支付服務(wù) paymentService.processPayment(order.getPayment()); } catch (Exception e) { log.error("支付失敗,但訂單已創(chuàng)建", e); // 處理支付失敗邏輯,但期望訂單依然保存 } } } @Service public class PaymentService { // 正確:使用REQUIRES_NEW創(chuàng)建獨(dú)立事務(wù) @Transactional(propagation = Propagation.REQUIRES_NEW) public void processPayment(Payment payment) { paymentMapper.insert(payment); throw new RuntimeException("支付處理失敗"); // 只有支付事務(wù)會(huì)回滾,不影響外部訂單事務(wù) } }
傳播行為選擇指南:
1. 如果希望內(nèi)部方法的異常不影響外部事務(wù):使用REQUIRES_NEW
2. 如果希望內(nèi)部方法的回滾不影響外部事務(wù),但共享同一連接:使用NESTED
(注意,這需要數(shù)據(jù)庫(kù)支持保存點(diǎn))
3. 如果希望完全共享外部事務(wù)的命運(yùn):使用REQUIRED
七、未被Spring管理的類
問(wèn)題現(xiàn)象
在類上添加了@Transactional
注解,但事務(wù)沒(méi)有生效。
// 未被Spring容器管理! public class UserService { @Autowired private UserMapper userMapper; @Transactional public void createUser(User user) { userMapper.insert(user); throw new RuntimeException("測(cè)試"); // 事務(wù)不會(huì)回滾 } }
原理分析
Spring事務(wù)是通過(guò)AOP實(shí)現(xiàn)的,只有被Spring容器管理的Bean才能被代理,進(jìn)而應(yīng)用事務(wù)切面。如果類沒(méi)有被Spring正確識(shí)別為Bean,@Transactional
注解就無(wú)法生效。
常見(jiàn)的原因包括:
1. 類上缺少@Component
、@Service
等注解
2. 類沒(méi)有被組件掃描到
3. 類是通過(guò)new
關(guān)鍵字直接創(chuàng)建的實(shí)例
解決方案
確保類被Spring容器管理:
@Service // 正確:添加@Service注解 public class UserService { @Autowired private UserMapper userMapper; @Transactional public void createUser(User user) { userMapper.insert(user); throw new RuntimeException("測(cè)試"); // 現(xiàn)在事務(wù)會(huì)正常回滾 } }
同時(shí),確保組件掃描配置正確:
@Configuration @ComponentScan("com.example.service") // 確保包路徑正確 public class AppConfig { // ... }
八、事務(wù)管理器配置錯(cuò)誤
問(wèn)題現(xiàn)象
使用了正確的事務(wù)注解,但事務(wù)不生效或者出現(xiàn)異常。
原理分析
Spring支持多種事務(wù)管理器,針對(duì)不同的持久化技術(shù):
- •
DataSourceTransactionManager
:適用于JDBC和MyBatis - •
JpaTransactionManager
:適用于JPA - •
HibernateTransactionManager
:適用于Hibernate
如果配置了錯(cuò)誤的事務(wù)管理器,或者在多數(shù)據(jù)源環(huán)境中未指定正確的事務(wù)管理器,會(huì)導(dǎo)致事務(wù)失效。
解決方案
單數(shù)據(jù)源環(huán)境:確保配置正確的事務(wù)管理器
@Configuration @EnableTransactionManagement public class DatabaseConfig { @Bean public DataSource dataSource() { // 數(shù)據(jù)源配置... return new HikariDataSource(); } @Bean public PlatformTransactionManager transactionManager(DataSource dataSource) { // 使用與持久化技術(shù)匹配的事務(wù)管理器 return new DataSourceTransactionManager(dataSource); } }
多數(shù)據(jù)源環(huán)境:指定使用的事務(wù)管理器
@Configuration @EnableTransactionManagement public class MultiDatabaseConfig { @Bean public DataSource primaryDataSource() { // 主數(shù)據(jù)源配置... return new HikariDataSource(); } @Bean public DataSource secondaryDataSource() { // 次數(shù)據(jù)源配置... return new HikariDataSource(); } @Bean public PlatformTransactionManager primaryTransactionManager() { return new DataSourceTransactionManager(primaryDataSource()); } @Bean public PlatformTransactionManager secondaryTransactionManager() { return new DataSourceTransactionManager(secondaryDataSource()); } } @Service public class UserService { // 指定使用的事務(wù)管理器 @Transactional(transactionManager = "primaryTransactionManager") public void createUser(User user) { // 使用主數(shù)據(jù)源的操作 } @Transactional(transactionManager = "secondaryTransactionManager") public void createUserLog(UserLog log) { // 使用次數(shù)據(jù)源的操作 } }
總結(jié)
Spring事務(wù)是一個(gè)強(qiáng)大的特性,但也隱藏著許多陷阱。理解這些陷阱的原理,可以幫助你更好地利用Spring事務(wù),構(gòu)建更加健壯的應(yīng)用程序。
到此這篇關(guān)于Spring中事務(wù)失效的8種常見(jiàn)使用場(chǎng)景避坑指南的文章就介紹到這了,更多相關(guān)Spring事務(wù)失效內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
阿里dubbo出錯(cuò)提示Thread pool is EXHAUSTED問(wèn)題及解決方法
這篇文章主要介紹了阿里dubbo出錯(cuò)提示Thread pool is EXHAUSTED的問(wèn)題及解決方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-08-08淺談java對(duì)象結(jié)構(gòu) 對(duì)象頭 Markword
這篇文章主要介紹了淺談java對(duì)象結(jié)構(gòu) 對(duì)象頭 Markword,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-10-10Knife4j的請(qǐng)求示例當(dāng)中有很多空白行的問(wèn)題解決辦法
這篇文章主要介紹了Knife4j的請(qǐng)求示例當(dāng)中有很多空白行的問(wèn)題解決辦法,按正常來(lái)說(shuō)不應(yīng)該有上方的空白,當(dāng)然如果只是查看我也不至于非要解決他,主要是假如接口是json傳參,調(diào)試界面都沒(méi)辦法修改參數(shù),遇到同樣問(wèn)題的同學(xué)可以參考閱讀本文2024-09-09Java數(shù)據(jù)結(jié)構(gòu)和算法之冒泡排序(動(dòng)力節(jié)點(diǎn)Java學(xué)院整理)
冒泡排序(Bubble Sort)是一種簡(jiǎn)單的排序算法。本文重點(diǎn)給大家介紹java數(shù)據(jù)結(jié)構(gòu)和算法之冒泡排序,非常不錯(cuò),具有參考借鑒價(jià)值,需要的的朋友參考下吧2017-04-04Java中為什么this可以調(diào)用當(dāng)前實(shí)例
本文主要介紹了為什么可以通過(guò)this關(guān)鍵字訪問(wèn)到當(dāng)前對(duì)象呢,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-07-07基于Java代碼實(shí)現(xiàn)判斷春節(jié)、端午節(jié)、中秋節(jié)等法定節(jié)假日的方法
這篇文章主要介紹了基于Java代碼實(shí)現(xiàn)判斷春節(jié)、端午節(jié)、中秋節(jié)等法定節(jié)假日的方法 的相關(guān)資料,需要的朋友可以參考下2016-01-01Java語(yǔ)言實(shí)現(xiàn)非遞歸實(shí)現(xiàn)樹(shù)的前中后序遍歷總結(jié)
今天小編就為大家分享一篇關(guān)于Java語(yǔ)言實(shí)現(xiàn)非遞歸實(shí)現(xiàn)樹(shù)的前中后序遍歷總結(jié),小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2019-01-01