1.錯誤的訪問權限
2.方法被定義成final的
3.方法內部調用
4.當前實體沒有被spring管理
5.錯誤的spring事務傳播特性
6.數據庫不支持事務
7.自己吞掉了異常
8.拋出的異常不正確
9.多線程調用
10.嵌套事務多回滾了
對于從事java開發工作的同學來說,spring的事務肯定再熟悉不過了。在某些業務場景下,如果同時有多張表的寫入操作,為了保證操作的原子性(要么同時成功,要么同時失敗)避免數據不一致的情況,我們一般都會使用spring事務。
沒錯,spring事務大多數情況下,可以滿足我們的業務需求。但是今天我要告訴大家的是,它有很多坑,稍不注意事務就會失效。
不信,我們一起看看。
1.錯誤的訪問權限
@Service publicclassUserService{ @Autowired privateUserMapperuserMapper; @Transactional privatevoidadd(UserModeluserModel){ userMapper.insertUser(userModel); } }
我們可以看到add方法的訪問權限被定義成了private,這樣會導致事務失效,spring要求被代理方法必須是public的。
AbstractFallbackTransactionAttributeSource類的computeTransactionAttribute方法中有個判斷,如果目標方法不是public,則TransactionAttribute返回null,即不支持事務。
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的,這樣會導致spring aop生成的代理對象不能復寫該方法,而讓事務失效。
3.方法內部調用
@Service publicclassUserService{ @Autowired privateUserMapperuserMapper; @Transactional publicvoidadd(UserModeluserModel){ userMapper.insertUser(userModel); updateStatus(userModel); } @Transactional publicvoidupdateStatus(UserModeluserModel){ //doSameThing(); } }
我們看到在事務方法add中,直接調用事務方法updateStatus。從前面介紹的內容可以知道,updateStatus方法擁有事務的能力是因為spring aop生成代理了對象,但是這種方法直接調用了this對象的方法,所以updateStatus方法不會生成事務。
4.當前實體沒有被spring管理
//@Service publicclassUserService{ @Autowired privateUserMapperuserMapper; @Transactional publicvoidadd(UserModeluserModel){ userMapper.insertUser(userModel); } }
我們可以看到UserService類沒有定義@Service注解,即沒有交給spring管理bean實例,所以它的add方法也不會生成事務。
5.錯誤的spring事務傳播特性
@Service publicclassUserService{ @Autowired privateUserMapperuserMapper; @Transactional(propagation=Propagation.NEVER) publicvoidadd(UserModeluserModel){ userMapper.insertUser(userModel); } }
我們可以看到add方法的事務傳播特性定義成了Propagation.NEVER,這種類型的傳播特性不支持事務,如果有事務則會拋異常。只有這三種傳播特性才會創建新事務:PROPAGATION_REQUIRED,PROPAGATION_REQUIRES_NEW,PROPAGATION_NESTED。
6.數據庫不支持事務
msql8以前的版本數據庫引擎是支持myslam和innerdb的。我以前也用過,對應查多寫少的單表操作,可能會把表的數據庫引擎定義成myslam,這樣可以提升查詢效率。但是,要千萬記得一件事情,myslam只支持表鎖,并且不支持事務。所以,對這類表的寫入操作事務會失效。
7.自己吞掉了異常
@Slf4j @Service publicclassUserService{ @Autowired privateUserMapperuserMapper; @Transactional publicvoidadd(UserModeluserModel){ try{ userMapper.insertUser(userModel); }catch(Exceptione){ log.error(e.getMessage(),e); } } }
這種情況下事務不會回滾,因為開發者自己捕獲了異常,又沒有拋出。事務的AOP無法捕獲異常,導致即使出現了異常,事務也不會回滾。
8.拋出的異常不正確
@Slf4j @Service publicclassUserService{ @Autowired privateUserMapperuserMapper; @Transactional publicvoidadd(UserModeluserModel)throwsException{ try{ userMapper.insertUser(userModel); }catch(Exceptione){ log.error(e.getMessage(),e); thrownewException(e); } } }
這種情況下,開發人員自己捕獲了異常,又拋出了異常:Exception,事務也不會回滾。因為spring事務,默認情況下只會回滾RuntimeException(運行時異常)和Error(錯誤),不會回滾Exception。
9.多線程調用
@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表數據"); } }
我們可以看到事務方法add中,調用了事務方法doOtherThing,但是事務方法doOtherThing是在另外一個線程中調用的,這樣會導致兩個事務方法不在同一個線程中,獲取到的數據庫連接不一樣,從而是兩個不同的事務。如果想doOtherThing方法中拋了異常,add方法也回滾是不可能的。
如果看過spring事務源碼的朋友,可能會知道spring的事務是通過數據庫連接來實現的。當前線程中保存了一個map,key是數據源,value是數據庫連接。
privatestaticfinalThreadLocal
我們說的同一個事務,其實是指同一個數據庫連接,只有擁有同一個數據庫連接才能同時提交和回滾。如果在不同的線程,拿到的數據庫連接肯定是不一樣的,所以是不同的事務。
10.嵌套事務多回滾了
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表數據"); } }
這種情況使用了嵌套的內部事務,原本是希望調用roleService.doOtherThing方法時,如果出現了異常,只回滾doOtherThing方法里的內容,不回滾 userMapper.insertUser里的內容,即回滾保存點。。但事實是,insertUser也回滾了。
why?
因為doOtherThing方法出現了異常,沒有手動捕獲,會繼續往上拋,到外層add方法的代理方法中捕獲了異常。所以,這種情況是直接回滾了整個事務,不只回滾單個保存點。
怎么樣才能只回滾保存點呢?
@Slf4j @Service publicclassUserService{ @Autowired privateUserMapperuserMapper; @Autowired privateRoleServiceroleService; @Transactional publicvoidadd(UserModeluserModel)throwsException{ userMapper.insertUser(userModel); try{ roleService.doOtherThing(); }catch(Exceptione){ log.error(e.getMessage(),e); } } }
在代碼中手動把內部嵌套事務放在try/catch中,并且不繼續往拋異常。
介紹到這里,你會發現spring事務的坑還是挺多的~
-
JAVA
+關注
關注
19文章
2967瀏覽量
104749 -
數據庫
+關注
關注
7文章
3799瀏覽量
64388 -
spring
+關注
關注
0文章
340瀏覽量
14343
原文標題:發現一個Spring事務的巨坑bug,可是官方都不承認?大家來評評理!
文章出處:【微信號:芋道源碼,微信公眾號:芋道源碼】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論