1.錯(cuò)誤的訪問(wèn)權(quán)限
2.方法被定義成final的
3.方法內(nèi)部調(diào)用
4.當(dāng)前實(shí)體沒(méi)有被spring管理
5.錯(cuò)誤的spring事務(wù)傳播特性
6.數(shù)據(jù)庫(kù)不支持事務(wù)
7.自己吞掉了異常
8.拋出的異常不正確
9.多線程調(diào)用
10.嵌套事務(wù)多回滾了
對(duì)于從事java開發(fā)工作的同學(xué)來(lái)說(shuō),spring的事務(wù)肯定再熟悉不過(guò)了。在某些業(yè)務(wù)場(chǎng)景下,如果同時(shí)有多張表的寫入操作,為了保證操作的原子性(要么同時(shí)成功,要么同時(shí)失?。┍苊鈹?shù)據(jù)不一致的情況,我們一般都會(huì)使用spring事務(wù)。
沒(méi)錯(cuò),spring事務(wù)大多數(shù)情況下,可以滿足我們的業(yè)務(wù)需求。但是今天我要告訴大家的是,它有很多坑,稍不注意事務(wù)就會(huì)失效。
不信,我們一起看看。
1.錯(cuò)誤的訪問(wèn)權(quán)限
@Service publicclassUserService{ @Autowired privateUserMapperuserMapper; @Transactional privatevoidadd(UserModeluserModel){ userMapper.insertUser(userModel); } }
我們可以看到add方法的訪問(wèn)權(quán)限被定義成了private,這樣會(huì)導(dǎo)致事務(wù)失效,spring要求被代理方法必須是public的。
AbstractFallbackTransactionAttributeSource類的computeTransactionAttribute方法中有個(gè)判斷,如果目標(biāo)方法不是public,則TransactionAttribute返回null,即不支持事務(wù)。
protectedTransactionAttributecomputeTransactionAttribute(Methodmethod,@NullableClass>targetClass){ //Don'tallowno-publicmethodsasrequired. if(allowPublicMethodsOnly()&&!Modifier.isPublic(method.getModifiers())){ returnnull; } //Themethodmaybeonaninterface,butweneedattributesfromthetargetclass. //Ifthetargetclassisnull,themethodwillbeunchanged. MethodspecificMethod=AopUtils.getMostSpecificMethod(method,targetClass); //Firsttryisthemethodinthetargetclass. TransactionAttributetxAttr=findTransactionAttribute(specificMethod); if(txAttr!=null){ returntxAttr; } //Secondtryisthetransactionattributeonthetargetclass. txAttr=findTransactionAttribute(specificMethod.getDeclaringClass()); if(txAttr!=null&&ClassUtils.isUserLevelMethod(method)){ returntxAttr; } if(specificMethod!=method){ //Fallbackistolookattheoriginalmethod. txAttr=findTransactionAttribute(method); if(txAttr!=null){ returntxAttr; } //Lastfallbackistheclassoftheoriginalmethod. txAttr=findTransactionAttribute(method.getDeclaringClass()); if(txAttr!=null&&ClassUtils.isUserLevelMethod(method)){ returntxAttr; } } returnnull; }
2.方法被定義成final的
@Service publicclassUserService{ @Autowired privateUserMapperuserMapper; @Transactional publicfinalvoidadd(UserModeluserModel){ userMapper.insertUser(userModel); } }
我們可以看到add方法被定義成了final的,這樣會(huì)導(dǎo)致spring aop生成的代理對(duì)象不能復(fù)寫該方法,而讓事務(wù)失效。
3.方法內(nèi)部調(diào)用
@Service publicclassUserService{ @Autowired privateUserMapperuserMapper; @Transactional publicvoidadd(UserModeluserModel){ userMapper.insertUser(userModel); updateStatus(userModel); } @Transactional publicvoidupdateStatus(UserModeluserModel){ //doSameThing(); } }
我們看到在事務(wù)方法add中,直接調(diào)用事務(wù)方法updateStatus。從前面介紹的內(nèi)容可以知道,updateStatus方法擁有事務(wù)的能力是因?yàn)閟pring aop生成代理了對(duì)象,但是這種方法直接調(diào)用了this對(duì)象的方法,所以u(píng)pdateStatus方法不會(huì)生成事務(wù)。
4.當(dāng)前實(shí)體沒(méi)有被spring管理
//@Service publicclassUserService{ @Autowired privateUserMapperuserMapper; @Transactional publicvoidadd(UserModeluserModel){ userMapper.insertUser(userModel); } }
我們可以看到UserService類沒(méi)有定義@Service注解,即沒(méi)有交給spring管理bean實(shí)例,所以它的add方法也不會(huì)生成事務(wù)。
5.錯(cuò)誤的spring事務(wù)傳播特性
@Service publicclassUserService{ @Autowired privateUserMapperuserMapper; @Transactional(propagation=Propagation.NEVER) publicvoidadd(UserModeluserModel){ userMapper.insertUser(userModel); } }
我們可以看到add方法的事務(wù)傳播特性定義成了Propagation.NEVER,這種類型的傳播特性不支持事務(wù),如果有事務(wù)則會(huì)拋異常。只有這三種傳播特性才會(huì)創(chuàng)建新事務(wù):PROPAGATION_REQUIRED,PROPAGATION_REQUIRES_NEW,PROPAGATION_NESTED。
6.數(shù)據(jù)庫(kù)不支持事務(wù)
msql8以前的版本數(shù)據(jù)庫(kù)引擎是支持myslam和innerdb的。我以前也用過(guò),對(duì)應(yīng)查多寫少的單表操作,可能會(huì)把表的數(shù)據(jù)庫(kù)引擎定義成myslam,這樣可以提升查詢效率。但是,要千萬(wàn)記得一件事情,myslam只支持表鎖,并且不支持事務(wù)。所以,對(duì)這類表的寫入操作事務(wù)會(huì)失效。
7.自己吞掉了異常
@Slf4j @Service publicclassUserService{ @Autowired privateUserMapperuserMapper; @Transactional publicvoidadd(UserModeluserModel){ try{ userMapper.insertUser(userModel); }catch(Exceptione){ log.error(e.getMessage(),e); } } }
這種情況下事務(wù)不會(huì)回滾,因?yàn)?a target="_blank">開發(fā)者自己捕獲了異常,又沒(méi)有拋出。事務(wù)的AOP無(wú)法捕獲異常,導(dǎo)致即使出現(xiàn)了異常,事務(wù)也不會(huì)回滾。
8.拋出的異常不正確
@Slf4j @Service publicclassUserService{ @Autowired privateUserMapperuserMapper; @Transactional publicvoidadd(UserModeluserModel)throwsException{ try{ userMapper.insertUser(userModel); }catch(Exceptione){ log.error(e.getMessage(),e); thrownewException(e); } } }
這種情況下,開發(fā)人員自己捕獲了異常,又拋出了異常:Exception,事務(wù)也不會(huì)回滾。因?yàn)閟pring事務(wù),默認(rèn)情況下只會(huì)回滾RuntimeException(運(yùn)行時(shí)異常)和Error(錯(cuò)誤),不會(huì)回滾Exception。
9.多線程調(diào)用
@Slf4j @Service publicclassUserService{ @Autowired privateUserMapperuserMapper; @Autowired privateRoleServiceroleService; @Transactional publicvoidadd(UserModeluserModel)throwsException{ userMapper.insertUser(userModel); newThread(()->{ roleService.doOtherThing(); }).start(); } } @Service publicclassRoleService{ @Transactional publicvoiddoOtherThing(){ System.out.println("保存role表數(shù)據(jù)"); } }
我們可以看到事務(wù)方法add中,調(diào)用了事務(wù)方法doOtherThing,但是事務(wù)方法doOtherThing是在另外一個(gè)線程中調(diào)用的,這樣會(huì)導(dǎo)致兩個(gè)事務(wù)方法不在同一個(gè)線程中,獲取到的數(shù)據(jù)庫(kù)連接不一樣,從而是兩個(gè)不同的事務(wù)。如果想doOtherThing方法中拋了異常,add方法也回滾是不可能的。
如果看過(guò)spring事務(wù)源碼的朋友,可能會(huì)知道spring的事務(wù)是通過(guò)數(shù)據(jù)庫(kù)連接來(lái)實(shí)現(xiàn)的。當(dāng)前線程中保存了一個(gè)map,key是數(shù)據(jù)源,value是數(shù)據(jù)庫(kù)連接。
privatestaticfinalThreadLocal
我們說(shuō)的同一個(gè)事務(wù),其實(shí)是指同一個(gè)數(shù)據(jù)庫(kù)連接,只有擁有同一個(gè)數(shù)據(jù)庫(kù)連接才能同時(shí)提交和回滾。如果在不同的線程,拿到的數(shù)據(jù)庫(kù)連接肯定是不一樣的,所以是不同的事務(wù)。
10.嵌套事務(wù)多回滾了
publicclassUserService{ @Autowired privateUserMapperuserMapper; @Autowired privateRoleServiceroleService; @Transactional publicvoidadd(UserModeluserModel)throwsException{ userMapper.insertUser(userModel); roleService.doOtherThing(); } } @Service publicclassRoleService{ @Transactional(propagation=Propagation.NESTED) publicvoiddoOtherThing(){ System.out.println("保存role表數(shù)據(jù)"); } }
這種情況使用了嵌套的內(nèi)部事務(wù),原本是希望調(diào)用roleService.doOtherThing方法時(shí),如果出現(xiàn)了異常,只回滾doOtherThing方法里的內(nèi)容,不回滾 userMapper.insertUser里的內(nèi)容,即回滾保存點(diǎn)。。但事實(shí)是,insertUser也回滾了。
why?
因?yàn)閐oOtherThing方法出現(xiàn)了異常,沒(méi)有手動(dòng)捕獲,會(huì)繼續(xù)往上拋,到外層add方法的代理方法中捕獲了異常。所以,這種情況是直接回滾了整個(gè)事務(wù),不只回滾單個(gè)保存點(diǎn)。
怎么樣才能只回滾保存點(diǎn)呢?
@Slf4j @Service publicclassUserService{ @Autowired privateUserMapperuserMapper; @Autowired privateRoleServiceroleService; @Transactional publicvoidadd(UserModeluserModel)throwsException{ userMapper.insertUser(userModel); try{ roleService.doOtherThing(); }catch(Exceptione){ log.error(e.getMessage(),e); } } }
在代碼中手動(dòng)把內(nèi)部嵌套事務(wù)放在try/catch中,并且不繼續(xù)往拋異常。
介紹到這里,你會(huì)發(fā)現(xiàn)spring事務(wù)的坑還是挺多的~
-
JAVA
+關(guān)注
關(guān)注
19文章
2975瀏覽量
105187 -
數(shù)據(jù)庫(kù)
+關(guān)注
關(guān)注
7文章
3851瀏覽量
64711 -
spring
+關(guān)注
關(guān)注
0文章
340瀏覽量
14394
原文標(biāo)題:發(fā)現(xiàn)一個(gè)Spring事務(wù)的巨坑bug,可是官方都不承認(rèn)?大家來(lái)評(píng)評(píng)理!
文章出處:【微信號(hào):芋道源碼,微信公眾號(hào):芋道源碼】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
Spring事務(wù)失效的十種常見(jiàn)場(chǎng)景
什么是java spring
詳解Spring事務(wù)管理
發(fā)現(xiàn)Tardis的PDA的一個(gè)BUG怎么解決?
啟動(dòng)Spring Boot項(xiàng)目應(yīng)用的三種方法
蘋果iOS 10.2默默修復(fù)了兩個(gè)未被發(fā)現(xiàn)的神級(jí)BUG,你知道?
spring中聲明式事務(wù)實(shí)現(xiàn)原理猜想
淺談Spring事務(wù)的那些坑
淺談Spring事務(wù)底層原理
8個(gè)Spring事務(wù)失效的場(chǎng)景介紹
![8<b class='flag-5'>個(gè)</b><b class='flag-5'>Spring</b><b class='flag-5'>事務(wù)</b>失效的場(chǎng)景介紹](https://file1.elecfans.com//web2/M00/82/A8/wKgZomRcVeKAWOClAAEG7su8Lcc137.jpg)
評(píng)論