解讀@Transactional失效的幾種情況
Spring事務(wù)管理方式
- 編碼式事務(wù)管理:將事務(wù)控制代碼編寫(xiě)在業(yè)務(wù)代碼之中。
- 聲明式事務(wù)管理:基于AOP(面向切面編程),事務(wù)管理與業(yè)務(wù)邏輯解耦。
聲明式事務(wù)管理的兩種實(shí)現(xiàn):
- (1)在配置文件(xml)中配置。
- (2)基于@Transactional注解。
@Transactional使用起來(lái)方便,但也需要注意引起@Transactional失效的場(chǎng)景,本文總結(jié)了七種情況,下面進(jìn)行逐一分析:
一、數(shù)據(jù)庫(kù)本身不支持
MySql 的 MyISAM 引擎不支持回滾,如果需要自動(dòng)回滾事務(wù),需要將MySql的引擎設(shè)置成InnoDB;
二、注解的方法是否為public
//@Transactional注解在private方法上會(huì)失效 @Transactional private void deleteUser() throws MyException{ userMapper.deleteUserA(); int i = 1/0; userMapper.deleteUserB(); }
idea直接會(huì)給出提示Methods annotated with ‘@Transactional’ must be overridable ,原理很簡(jiǎn)單,private修飾的方式,spring無(wú)法生成動(dòng)態(tài)代理,AOP代理分別在intercept()和invoke()方法判斷是否進(jìn)行事務(wù)攔截,這兩個(gè)方法都會(huì)間接調(diào)用AbstractFallbackTransactionAttributeSource類(lèi)的computeTransactionAttribute方法來(lái)獲取事務(wù)控制的相關(guān)屬性。
這其中有以下一段代碼
/** * Same signature as {@link #getTransactionAttribute}, but doesn't cache the result. * {@link #getTransactionAttribute} is effectively a caching decorator for this method. * <p>As of 4.1.8, this method can be overridden. * @since 4.1.8 * @see #getTransactionAttribute */ protected TransactionAttribute computeTransactionAttribute(Method method, Class<?> targetClass) { // Don't allow no-public methods as required. if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) { return null; } //... }
這段代碼會(huì)導(dǎo)致no-public的方法無(wú)法進(jìn)入事務(wù)控制,所以一定要確保自己需要進(jìn)行事務(wù)控制的方法包含public修飾符。
三、異常處理不當(dāng)
當(dāng)異常被捕獲后,并且沒(méi)有再拋出,那么deleteUserA是不會(huì)回滾的,例如:
@Transactional public void deleteUser() { userMapper.deleteUserA(); try { int i = 1 / 0; userMapper.deleteUserB(); } catch (Exception e) { e.printStackTrace(); } }
異步雖然拋出了,但是拋出的是非RuntimeException類(lèi)型的異常,依舊不會(huì)生效,例如:
@Transactional public void deleteUser() throws MyException{ userMapper.deleteUserA(); try { int i = 1 / 0; userMapper.deleteUserB(); } catch (Exception e) { throw new MyException(); } }
注解為事務(wù)范圍的方法中,事務(wù)的回滾僅僅對(duì)于unchecked的異常有效。對(duì)于checked異常無(wú)效。也就是說(shuō)事務(wù)回滾僅僅發(fā)生在,出現(xiàn)RuntimeException或Error的時(shí)候。通俗一點(diǎn)就是:代碼中出現(xiàn)的空指針等異常,會(huì)被回滾。而文件讀寫(xiě)、網(wǎng)絡(luò)超時(shí)問(wèn)題等,spring就沒(méi)法回滾了。
解決方案:如果指定了回滾異常類(lèi)型為Exception,那么就可以回滾Checked類(lèi)型異常了。
@Transactional(rollbackFor = Exception.class)
java里面將派生于Error或者RuntimeException(比如空指針,1/0)的異常稱(chēng)為unchecked異常,其他繼承自java.lang.Exception得異常統(tǒng)稱(chēng)為Checked Exception,如IOException、TimeoutException等
四、方法內(nèi)部直接調(diào)用
如果先調(diào)用deleteUser(),那么deleteUserA()是不會(huì)回滾的,其原因就是@Transactional根本沒(méi)生成代理,例如:
public void deleteUser() throws MyException{ deleteUser2(); // 事物失效 } @Transactional public void deleteUser2() throws MyException{ userMapper.deleteUserA(); int i = 1 / 0; userMapper.deleteUserB(); }
五、多數(shù)據(jù)源事物配置問(wèn)題
項(xiàng)目中沒(méi)有配置事務(wù)管理器,需要在配置類(lèi)或者配置文件中配置,因?yàn)轫?xiàng)目是多數(shù)據(jù)源的,所以要區(qū)別配置不同數(shù)據(jù)源的事務(wù)管理器,如下:
@Primary @Bean(name = "db1") public DataSource getDataSource() { return createDataSource(); } @Bean(name = "db1TransactionManager") public PlatformTransactionManager txManager(@Qualifier("db1") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } @Bean(name = "db2") public DataSource getDataSource() { return buildDataSource(); } @Bean(name = "db2TransactionManager") public PlatformTransactionManager txManager(@Qualifier("db2") DataSource dataSource) { return new DataSourceTransactionManager(dataSource); }
可以看到,兩個(gè)事務(wù)管理器配置了不同的beanName,接下來(lái)只需要 在需要事務(wù)控制的位置加上該事務(wù)管理器的name就可以完美解決!
@Override @Transactional(value = "db1TransactionManager",rollbackFor = Exception.class) public int updateOrInsert(BaseRequest<BankTemplateDto> param) { ... }
六、新開(kāi)啟一個(gè)線程
如下的方式deleteUserA()也不會(huì)回滾,因?yàn)閟pring實(shí)現(xiàn)事務(wù)的原理是通過(guò)ThreadLocal把數(shù)據(jù)庫(kù)連接綁定到當(dāng)前線程中,新開(kāi)啟一個(gè)線程獲取到的連接就不是同一個(gè)了,例如:
@Transactional public void deleteUser() throws MyException{ userMapper.deleteUserA(); try { //休眠1秒,保證deleteUserA先執(zhí)行 Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(() -> { int i = 1/0; userMapper.deleteUserB(); }).start(); }
七、事務(wù)傳播屬性設(shè)置錯(cuò)誤
注意傳播屬性的設(shè)置,一般情況下,propagation屬性無(wú)需配置。會(huì)使用默認(rèn)配置,即:PROPAGATION_REQUIRED,有些propagation屬性會(huì)導(dǎo)致事務(wù)不會(huì)觸發(fā),一定要注意:
- PROPAGATION_SUPPORTS: 如果存在事務(wù),則進(jìn)入事務(wù);否則,以非事務(wù)方式運(yùn)行。
- PROPAGATION_NOT_SUPPORTED: 如果存在事務(wù),則掛起事務(wù),并以非事務(wù)方式運(yùn)行。
- PROPAGATION_NEVER: 以非事務(wù)形式運(yùn)行,如果存在事務(wù),則拋出異常。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Springboot中實(shí)現(xiàn)接口冪等性的4種方案小結(jié)
本文主要介紹了Springboot中實(shí)現(xiàn)接口冪等性,包含數(shù)據(jù)庫(kù)的冪等,數(shù)據(jù)庫(kù)的冪等,Redis的冪等性和Token + 時(shí)間戳的冪等性,具有一定的參考價(jià)值,感興趣的可以了解一下2024-03-03Future與FutureTask接口實(shí)現(xiàn)示例詳解
這篇文章主要為大家介紹了Future與FutureTask接口實(shí)現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10基于Java驗(yàn)證jwt token代碼實(shí)例
這篇文章主要介紹了基于Java驗(yàn)證jwt token代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-12-12Spring Security認(rèn)證機(jī)制源碼層探究
SpringSecurity是基于Filter實(shí)現(xiàn)認(rèn)證和授權(quán),底層通過(guò)FilterChainProxy代理去調(diào)用各種Filter(Filter鏈),F(xiàn)ilter通過(guò)調(diào)用AuthenticationManager完成認(rèn)證 ,通過(guò)調(diào)用AccessDecisionManager完成授權(quán)2023-03-03springcloud feign調(diào)其他微服務(wù)時(shí)參數(shù)是對(duì)象的問(wèn)題
這篇文章主要介紹了springcloud feign調(diào)其他微服務(wù)時(shí)參數(shù)是對(duì)象的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03