Spring事務(wù)@Transactional注解四種不生效案例場(chǎng)景分析
背景
在我們工作中,經(jīng)常會(huì)用到 @Transactional 聲明事務(wù),不正確的使用姿勢(shì)會(huì)導(dǎo)致注解失效,下面就來(lái)分析四種最常見(jiàn)的@Transactional事務(wù)不生效的 Case:
類(lèi)內(nèi)部訪問(wèn):A 類(lèi)的 a1 方法沒(méi)有標(biāo)注 @Transactional,a2 方法標(biāo)注 @Transactional,在 a1 里面調(diào)用 a2;
私有方法:將 @Transactional 注解標(biāo)注在非 public 方法上;
異常不匹配:@Transactional 未設(shè)置 rollbackFor 屬性,方法返回 Exception 等異常;
多線程:主線程和子線程的調(diào)用,線程拋出異常。
示例代碼
UserDao 接口,操作數(shù)據(jù)庫(kù);UserController 實(shí)現(xiàn)業(yè)務(wù)邏輯,聲明事務(wù),調(diào)用 UserController.testSuccess 方法,事務(wù)聲明生效
// 提供的接口 public interface UserDao { // select * from user_test where uid = "#{uid}" public MyUser selectUserById(Integer uid); // update user_test set uname =#{uname},usex = #{usex} where uid = #{uid} public int updateUser(MyUser user); }
@Service public class UserController { @Autowired private UserDao userDao; public void update(Integer id) { MyUser user = new MyUser(); user.setUid(id); user.setUname("張三-testing"); user.setUsex("女"); userDao.updateUser(user); } public MyUser query(Integer id) { MyUser user = userDao.selectUserById(id); return user; } // 正常情況 @Transactional(rollbackFor = Exception.class) public void testSuccess() throws Exception { Integer id = 1; MyUser user = query(id); System.out.println("原記錄:" + user); update(id); throw new Exception("事務(wù)生效"); } }
為了快速說(shuō)明問(wèn)題,直接在controller中實(shí)現(xiàn)了業(yè)務(wù)邏輯和事務(wù)聲明,不代表生產(chǎn)環(huán)境中的代碼分層
1. 類(lèi)內(nèi)部訪問(wèn)
在類(lèi) UserController 中新增一個(gè)方法 testInteralCall():
public void testInteralCall() throws Exception { testSuccess(); throw new Exception("事務(wù)不生效:類(lèi)內(nèi)部訪問(wèn)"); }
這里 testInteralCall() 沒(méi)有標(biāo)注 @Transactional,我們?cè)倏匆幌聹y(cè)試用例:
public static void main(String[] args) throws Exception { ApplicationContext applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); UserController uc = (UserController) applicationContext.getBean("userController"); try { uc.testSuccess(); } finally { MyUser user = uc.query(1); System.out.println("修改后的記錄:" + user); } } // 輸出: // 原記錄:MyUser(uid=1, uname=張三, usex=女) // 修改后的記錄:MyUser(uid=1, uname=張三-testing, usex=女)
從上面的輸出可以看到,事務(wù)并沒(méi)有回滾,這個(gè)是什么原因呢?
因?yàn)?@Transactional 的工作機(jī)制是基于 AOP 實(shí)現(xiàn),AOP 是使用動(dòng)態(tài)代理實(shí)現(xiàn)的,如果通過(guò)代理直接調(diào)用 testSuccess(),通過(guò) AOP 會(huì)前后進(jìn)行增強(qiáng),增強(qiáng)的邏輯其實(shí)就是在 testSuccess() 的前后分別加上開(kāi)啟、提交事務(wù)的邏輯。
現(xiàn)在是通過(guò) testInteralCall() 去調(diào)用 testSuccess(),testSuccess() 前后不會(huì)進(jìn)行任何增強(qiáng)操作,也就是類(lèi)內(nèi)部調(diào)用,不會(huì)通過(guò)代理方式訪問(wèn)。
2. 私有方法
在私有方法上,添加 @Transactional 注解也不會(huì)生效:
@Transactional(rollbackFor = Exception.class) private void testPirvateMethod() throws Exception { Integer id = 1; MyUser user = query(id); System.out.println("原記錄:" + user); update(id); throw new Exception("測(cè)試事務(wù)生效"); }
直接使用時(shí),下面這種場(chǎng)景不太容易出現(xiàn),因?yàn)?IDEA 會(huì)有提醒,文案為: Methods annotated with '@Transactional' must be overridable,至于深層次的原理,源碼部分會(huì)給你解讀。
3. 異常不匹配
這里的 @Transactional 沒(méi)有設(shè)置 rollbackFor = Exception.class 屬性:
@Transactional public void testExceptionNotMatch() throws Exception { Integer id = 1; MyUser user = query(id); System.out.println("原記錄:" + user); update(id); throw new Exception("事務(wù)不生效:異常不匹配"); }
@Transactional 注解默認(rèn)處理運(yùn)行時(shí)異常,即只有拋出運(yùn)行時(shí)異常時(shí),才會(huì)觸發(fā)事務(wù)回滾,否則并不會(huì)回滾,至于深層次的原理,源碼部分會(huì)給你解讀。
4. 多線程
父線程拋出異常
父線程拋出異常,子線程不拋出異常:
public void testSuccess() throws Exception { Integer id = 1; MyUser user = query(id); System.out.println("原記錄:" + user); update(id); } @Transactional(rollbackFor = Exception.class) public void testMultThread() throws Exception { new Thread(new Runnable() { @SneakyThrows @Override public void run() { testSuccess(); } }).start(); throw new Exception("測(cè)試事務(wù)不生效"); }
父線程拋出線程,事務(wù)回滾,因?yàn)樽泳€程是獨(dú)立存在,和父線程不在同一個(gè)事務(wù)中,所以子線程的修改并不會(huì)被回滾
子線程拋出異常
父線程不拋出異常,子線程拋出異常:
public void testSuccess() throws Exception { Integer id = 1; MyUser user = query(id); System.out.println("原記錄:" + user); update(id); throw new Exception("測(cè)試事務(wù)不生效"); } @Transactional(rollbackFor = Exception.class) public void testMultThread() throws Exception { new Thread(new Runnable() { @SneakyThrows @Override public void run() { testSuccess(); } }).start(); }
由于子線程的異常不會(huì)被外部的線程捕獲,所以父線程不拋異常,事務(wù)回滾沒(méi)有生效。
源碼解讀
@Transactional 執(zhí)行機(jī)制
我們只看最核心的邏輯,代碼中的 interceptorOrInterceptionAdvice 就是 TransactionInterceptor 的實(shí)例,入?yún)⑹?this 對(duì)象。
紅色方框有一段注釋?zhuān)笾路g為 “它是一個(gè)攔截器,所以我們只需調(diào)用即可:在構(gòu)造此對(duì)象之前,將靜態(tài)地計(jì)算切入點(diǎn)。”
this 是 ReflectiveMethodInvocation 對(duì)象,成員對(duì)象包含 UserController 類(lèi)、testSuccess() 方法、入?yún)⒑痛韺?duì)象等。
進(jìn)入 invoke() 方法后:
前方高能!??!這里就是事務(wù)的核心邏輯,包括判斷事務(wù)是否開(kāi)啟、目標(biāo)方法執(zhí)行、事務(wù)回滾、事務(wù)提交。
private 導(dǎo)致事務(wù)不生效原因
在上面這幅圖中,第一個(gè)紅框區(qū)域調(diào)用了方法 getTransactionAttribute(),主要是為了獲取 txAttr 變量,它是用于讀取 @Transactional 的配置,如果這個(gè) txAttr = null,后面就不會(huì)走事務(wù)邏輯,我們看一下這個(gè)變量的含義:
我們直接進(jìn)入 getTransactionAttribute(),重點(diǎn)關(guān)注獲取事務(wù)配置的方法。
前方高能?。?!這里就是 private 導(dǎo)致事務(wù)不生效的原因所在,allowPublicMethodsOnly() 一直返回 false,所以重點(diǎn)只關(guān)注 isPublic() 方法。
異常不匹配原因
我們繼續(xù)回到事務(wù)的核心邏輯,因?yàn)橹鞣椒⊕伋?Exception() 異常,進(jìn)入事務(wù)回滾的邏輯:
進(jìn)入 rollbackOn() 方法,判斷該異常是否能進(jìn)行回滾,這個(gè)需要判斷主方法拋出的 Exception() 異常,是否在 @Transactional 的配置中:
我們進(jìn)入 getDepth() 看一下異常規(guī)則匹配邏輯,因?yàn)槲覀儗?duì) @Transactional 配置了 rollbackFor = Exception.class,所以能匹配成功:
示例中的 winner 不為 null,所以會(huì)跳過(guò)下面的環(huán)節(jié)。但是當(dāng) winner = null 時(shí),也就是沒(méi)有設(shè)置 rollbackFor 屬性時(shí),會(huì)走默認(rèn)的異常捕獲方式。
前方高能!??!這里就是異常不匹配原因的原因所在,我們看一下默認(rèn)的異常捕獲方式:
是不是豁然開(kāi)朗,當(dāng)沒(méi)有設(shè)置 rollbackFor 屬性時(shí),默認(rèn)只對(duì) RuntimeException 和 Error 的異常執(zhí)行回滾。
以上就是Spring事務(wù)@Transactional注解的四種不生效案例場(chǎng)景分析的詳細(xì)內(nèi)容,更多關(guān)于Spring事務(wù)@Transactional不生效的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- spring中12種@Transactional的失效場(chǎng)景(小結(jié))
- Spring中@Transactional用法詳細(xì)介紹
- springboot中事務(wù)管理@Transactional的注意事項(xiàng)與使用場(chǎng)景
- Spring @Transactional工作原理詳解
- Spring @Transactional注解失效解決方案
- spring @Transactional 無(wú)效的解決方案
- spring中@Transactional?注解失效的原因及解決辦法
- spring的@Transactional注解用法解讀
- spring中@Transactional注解和事務(wù)的實(shí)戰(zhàn)
相關(guān)文章
java8新特性之stream的collect實(shí)戰(zhàn)教程
這篇文章主要介紹了java8新特性之stream的collect實(shí)戰(zhàn)教程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-08-08Spring中的BeanFactory與FactoryBean區(qū)別詳解
這篇文章主要介紹了Spring中的BeanFactory與FactoryBean區(qū)別詳解,BeanFactory是一個(gè)接口,它是spring中的一個(gè)工廠,FactoryBean也是一個(gè)接口,實(shí)現(xiàn)了3個(gè)方法,通過(guò)重寫(xiě)其中方法自定義生成bean,需要的朋友可以參考下2024-01-01SpringMVC中的SimpleUrlHandlerMapping用法詳解
這篇文章主要介紹了SpringMVC中的SimpleUrlHandlerMapping用法詳解,SimpleUrlHandlerMapping是Spring MVC中適用性最強(qiáng)的Handler Mapping類(lèi),允許明確指定URL模式和Handler的映射關(guān)系,有兩種方式聲明SimpleUrlHandlerMapping,需要的朋友可以參考下2023-10-10java中使用Filter控制用戶(hù)登錄權(quán)限具體實(shí)例
java中使用Filter控制用戶(hù)登錄權(quán)限具體實(shí)例,需要的朋友可以參考一下2013-06-06Java面試題沖刺第十九天--數(shù)據(jù)庫(kù)(4)
這篇文章主要為大家分享了最有價(jià)值的三道關(guān)于數(shù)據(jù)庫(kù)的面試題,涵蓋內(nèi)容全面,包括數(shù)據(jù)結(jié)構(gòu)和算法相關(guān)的題目、經(jīng)典面試編程題等,感興趣的小伙伴們可以參考一下2021-08-08RabbitMQ排他性隊(duì)列Exclusive Queue詳解
這篇文章主要介紹了RabbitMQ排他性隊(duì)列Exclusive Queue詳解,如果你想創(chuàng)建一個(gè)只有自己可見(jiàn)的隊(duì)列,即不允許其它用戶(hù)訪問(wèn),RabbitMQ允許你將一個(gè)Queue聲明成為排他性的Exclusive Queue,需要的朋友可以參考下2023-08-08java實(shí)現(xiàn)外賣(mài)訂餐系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)外賣(mài)訂餐系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-01-01