spring中12種@Transactional的失效場(chǎng)景(小結(jié))
數(shù)據(jù)庫(kù)事務(wù)是后端開(kāi)發(fā)中不可缺少的一塊知識(shí)點(diǎn)。Spring為了更好的支撐我們進(jìn)行數(shù)據(jù)庫(kù)操作,在框架中支持了兩種事務(wù)管理的方式: 編程式事務(wù)聲明式事務(wù)
日常我們進(jìn)行業(yè)務(wù)開(kāi)發(fā)時(shí),基本上使用的都是聲明式事務(wù),即為使用@Transactional
注解的方式。
常規(guī)使用時(shí),Spring能幫我們很好的實(shí)現(xiàn)數(shù)據(jù)庫(kù)的ACID (這里需要注意哦,Spring只是進(jìn)行了編程上的事務(wù),最終數(shù)據(jù)上的事務(wù)還是有數(shù)據(jù)庫(kù)實(shí)現(xiàn)的) 。
但是,只要是人寫的代碼,就一定會(huì)有Bug。
如果我們不了解@Transactional
的失效場(chǎng)景或者說(shuō)踩坑點(diǎn),那么在業(yè)務(wù)開(kāi)發(fā)的過(guò)程中總是會(huì)出現(xiàn)一些匪夷所思的Bug。
同樣它也是面試時(shí)高頻的考點(diǎn)哦!
本文將羅列@Transactional
的失效場(chǎng)景,并分析其失效原因。
一、失效場(chǎng)景集一:代理不生效
Spring中對(duì)注解解析的尿性都是基于代理的,如果目標(biāo)方法無(wú)法被Spring代理到,那么它將無(wú)法被Spring進(jìn)行事務(wù)管理。
Spring生成代理的方式有兩種:
- 基于接口的JDK動(dòng)態(tài)代理,要求目標(biāo)代理類需要實(shí)現(xiàn)一個(gè)接口才能被代理
- 基于實(shí)現(xiàn)目標(biāo)類子類的CGLIB代理
Spring在2.0之前,目標(biāo)類如果實(shí)現(xiàn)了接口,則使用JDK動(dòng)態(tài)代理方式,否則通過(guò)CGLIB子類的方式生成代理。
而在2.0版本之后,如果不在配置文件中顯示的指定spring.aop.proxy-tartget-class
的值,默認(rèn)情況下生成代理的方式為CGLIB,如下圖
順著代理的思路,我們來(lái)看看哪些情況會(huì)因?yàn)榇聿簧?dǎo)致事務(wù)管控失敗。
(1)將注解標(biāo)注在接口方法上
@Transactional
是支持標(biāo)注在方法與類上的。一旦標(biāo)注在接口上,對(duì)應(yīng)接口實(shí)現(xiàn)類的代理方式如果是CGLIB,將通過(guò)生成子類的方式生成目標(biāo)類的代理,將無(wú)法解析到@Transactional
,從而事務(wù)失效。
這種錯(cuò)誤我們還是犯得比較少的,基本上我們都會(huì)將注解標(biāo)注在接口的實(shí)現(xiàn)類方法上,官方也不推薦這種。
(2)被final、static關(guān)鍵字修飾的類或方法
CGLIB是通過(guò)生成目標(biāo)類子類的方式生成代理類的,被final、static修飾后,無(wú)法繼承父類與父類的方法。
(3)類方法內(nèi)部調(diào)用
事務(wù)的管理是通過(guò)代理執(zhí)行的方式生效的,如果是方法內(nèi)部調(diào)用,將不會(huì)走代理邏輯,也就調(diào)用不到了。
例如
在createUser中調(diào)用了內(nèi)部方法createUser1,并且createUser1方法上設(shè)置了事務(wù)傳播策略為:REQUIRES_NEW,但是因?yàn)槭莾?nèi)部直接調(diào)用,createUser1不能不代理處理,無(wú)法進(jìn)行事務(wù)管理。在createUser1方法拋出異常后就插入數(shù)據(jù)失敗了。
但是這種操作在我們業(yè)務(wù)開(kāi)發(fā)的過(guò)程中貌似還挺常見(jiàn)的,怎么樣才能保證其成功呢?
方式1:新建一個(gè)Service,將方法遷移過(guò)去,有點(diǎn)麻瓜。
方式2:在當(dāng)前類注入自己,調(diào)用createUser1時(shí)通過(guò)注入的userService調(diào)用
方式3:通過(guò)AopContext.currentProxy()獲取代理對(duì)象
道理類似于方式2,就是為了通過(guò)代理來(lái)訪問(wèn)內(nèi)部方法
(4)當(dāng)前類沒(méi)有被Spring管理
這個(gè)沒(méi)什么好說(shuō)的,都沒(méi)有被Spring管理成為IOC容器中的一個(gè)bean,更別說(shuō)被事務(wù)切面代理到了。
這種Bug看上去比較蠢,但沒(méi)準(zhǔn)真的有人犯錯(cuò)。
二、失效場(chǎng)景集二:框架或底層不支持的功能
這類失效場(chǎng)景主要聚焦在框架本身在解析@Transactional
時(shí)的內(nèi)部支持。如果使用的場(chǎng)景本身就是框架不支持的,那事務(wù)也是無(wú)法生效的。
(1)非public修飾的方法
我們?cè)跇?biāo)有@Transactional
的任意方法上打個(gè)斷點(diǎn),在idea內(nèi)能看到事務(wù)切面點(diǎn)如下圖所示
點(diǎn)擊去這個(gè)方法,在開(kāi)頭有這么一個(gè)調(diào)用
繼續(xù)進(jìn)去
就能看到這么一句話了
不支持非public修飾的方法進(jìn)行事務(wù)管理。
(2)多線程調(diào)用
跟上面一樣的的操作,我們能夠逐層進(jìn)入到TransactionAspectSupport.prepareTransactionInfo
方法。
注意看以下這段話
從這里我們得知,事務(wù)信息是跟線程綁定的。
因此在多線程環(huán)境下,事務(wù)的信息都是獨(dú)立的,將會(huì)導(dǎo)致Spring在接管事務(wù)上出現(xiàn)差異。
這個(gè)場(chǎng)景我們要尤其注意!
給大家舉個(gè)例子
主線程A調(diào)用線程B保存Id為1的數(shù)據(jù),然后主線程A等待線程B執(zhí)行完成再通過(guò)線程A查詢id為1的數(shù)據(jù)。
這時(shí)你會(huì)發(fā)現(xiàn)在主線程A中無(wú)法查詢到id為1的數(shù)據(jù)。因?yàn)檫@兩個(gè)線程在不同的Spring事務(wù)中,本質(zhì)上會(huì)導(dǎo)致它們?cè)贛ysql中存在不同的事務(wù)中。
Mysql中通過(guò)MVCC保證了線程在快照讀時(shí)只讀取小于當(dāng)前事務(wù)號(hào)的數(shù)據(jù),在線程B顯然事務(wù)號(hào)是大于線程A的,因此查詢不到數(shù)據(jù)。
(3)數(shù)據(jù)庫(kù)本身不支持事務(wù)
比如Mysql的Myisam存儲(chǔ)引擎是不支持事務(wù)的,只有innodb存儲(chǔ)引擎才支持。
這個(gè)問(wèn)題出現(xiàn)的概率極其小,因?yàn)镸ysql5之后默認(rèn)情況下是使用innodb存儲(chǔ)引擎了。
但如果配置錯(cuò)誤或者是歷史項(xiàng)目,發(fā)現(xiàn)事務(wù)怎么配都不生效的時(shí)候,記得看看存儲(chǔ)引擎本身是否支持事務(wù)。
(4)未開(kāi)啟事務(wù)
這個(gè)也是一個(gè)比較麻瓜的問(wèn)題,在Springboot項(xiàng)目中已經(jīng)不存在了,已經(jīng)有DataSourceTransactionManagerAutoConfiguration默認(rèn)開(kāi)啟了事務(wù)管理。
但是在MVC項(xiàng)目中還需要在applicationContext.xml文件中,手動(dòng)配置事務(wù)相關(guān)參數(shù)。如果忘了配置,事務(wù)肯定是不會(huì)生效的。
三、失效場(chǎng)景集三:錯(cuò)誤使用@Transactional
注意啦注意啦,下面這幾種都是高頻會(huì)出現(xiàn)的Bug!
(1)錯(cuò)誤的傳播機(jī)制
Spring支持了7種傳播機(jī)制,分別為:
上面不支持事務(wù)的傳播機(jī)制為:PROPAGATION_SUPPORTS,PROPAGATION_NOT_SUPPORTED,PROPAGATION_NEVER。
如果配置了這三種傳播方式的話,在發(fā)生異常的時(shí)候,事務(wù)是不會(huì)回滾的。
(2)rollbackFor屬性設(shè)置錯(cuò)誤
默認(rèn)情況下事務(wù)僅回滾運(yùn)行時(shí)異常和Error,不回滾受檢異常(例如IOException)。
因此如果方法中拋出了IO異常,默認(rèn)情況下事務(wù)也會(huì)回滾失敗。
我們可以通過(guò)指定@Transactional(rollbackFor = Exception.class)
的方式進(jìn)行全異常捕獲。
(3)異常被內(nèi)部catch
UserService
UserService1
如上代碼UserService調(diào)用了UserService1中的方法,并且捕獲了UserService1中拋出的異常。
你將能看到控制臺(tái)出現(xiàn)這樣一個(gè)報(bào)錯(cuò):
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
默認(rèn)情況下標(biāo)注了@Transactional
注解的方法的事務(wù)傳播機(jī)制是REQUIRED,它的特性是支持當(dāng)前事務(wù),也就說(shuō)加入當(dāng)前事務(wù)。我們?cè)赨serService中開(kāi)始事務(wù),然后再UserService1中拋出異常回滾UserService中的事務(wù),將其標(biāo)記為只讀。
但是在UserSevice中我們捕獲了異常,此時(shí)UserService上的事務(wù)認(rèn)為正常提交事務(wù)。最后在提交時(shí)發(fā)現(xiàn)事務(wù)只讀,已經(jīng)被回滾,則拋出了上述異常。
因此這里如果需要對(duì)特定的異常進(jìn)行捕獲處理,記得再次將異常拋出,讓最外層的事務(wù)感知到。
(4)嵌套事務(wù)
上面是我想同時(shí)回滾UserService與UserService1。但是也會(huì)有這種場(chǎng)景只想回滾UserService1中報(bào)錯(cuò)的數(shù)據(jù)庫(kù)操作,不影響主邏輯UserService中的數(shù)據(jù)落庫(kù)。
有兩種方式可以實(shí)現(xiàn)上述邏輯:
1.直接在UserService1內(nèi)的整個(gè)方法用try/catch包住
2.在UserService1使用Propagation.REQUIRES_NEW傳播機(jī)制
四、總結(jié)
本文為大家分析@Transactional
注解使用過(guò)程中失效的12種場(chǎng)景
最后,@Transactional
注解雖香,但是復(fù)雜業(yè)務(wù)邏輯下,為了更好的管理事務(wù)與把控業(yè)務(wù)處理時(shí)事務(wù)的細(xì)粒度,我還是推薦大家使用編程式事務(wù)。
到此這篇關(guān)于spring中12種@Transactional的失效場(chǎng)景(小結(jié))的文章就介紹到這了,更多相關(guān)spring @Transactional 失效場(chǎng)景 內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Spring中@Transactional用法詳細(xì)介紹
- springboot中事務(wù)管理@Transactional的注意事項(xiàng)與使用場(chǎng)景
- Spring @Transactional工作原理詳解
- Spring @Transactional注解失效解決方案
- spring @Transactional 無(wú)效的解決方案
- spring中@Transactional?注解失效的原因及解決辦法
- Spring事務(wù)@Transactional注解四種不生效案例場(chǎng)景分析
- spring的@Transactional注解用法解讀
- spring中@Transactional注解和事務(wù)的實(shí)戰(zhàn)
相關(guān)文章
java必學(xué)必會(huì)之this關(guān)鍵字
java必學(xué)必會(huì)之this關(guān)鍵字,java中this的用法進(jìn)行了詳細(xì)的分析介紹,感興趣的小伙伴們可以參考一下2015-12-12SpringBoot JPA出現(xiàn)錯(cuò)誤:No identifier specified&nb
這篇文章主要介紹了SpringBoot JPA出現(xiàn)錯(cuò)誤:No identifier specified for en解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03springboot與vue詳解實(shí)現(xiàn)短信發(fā)送流程
隨著人工智能的不斷發(fā)展,機(jī)器學(xué)習(xí)這門技術(shù)也越來(lái)越重要,很多人都開(kāi)啟了學(xué)習(xí)機(jī)器學(xué)習(xí),本文就介紹了機(jī)器學(xué)習(xí)的基礎(chǔ)內(nèi)容2022-06-06Java中零拷貝和深拷貝的原理及實(shí)現(xiàn)探究(代碼示例)
深拷貝和零拷貝是兩個(gè)在 Java 中廣泛使用的概念,它們分別用于對(duì)象復(fù)制和數(shù)據(jù)傳輸優(yōu)化,下面將詳細(xì)介紹這兩個(gè)概念的原理,并給出相應(yīng)的 Java 代碼示例,感興趣的朋友一起看看吧2023-12-12Json轉(zhuǎn)list二層解析轉(zhuǎn)換代碼實(shí)例
這篇文章主要介紹了Json轉(zhuǎn)list二層解析轉(zhuǎn)換代碼實(shí)例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-12-12Java ArrayList的基本概念和作用及動(dòng)態(tài)數(shù)組的機(jī)制與性能
在Java中,ArrayList是一個(gè)實(shí)現(xiàn)了List接口的動(dòng)態(tài)數(shù)組,它可以根據(jù)需要自動(dòng)增加大小,因此可以存儲(chǔ)任意數(shù)量的元素,這篇文章主要介紹了探秘Java ArrayList的基本概念和作用及動(dòng)態(tài)數(shù)組的機(jī)制與性能,需要的朋友可以參考下2023-12-12使用webmagic實(shí)現(xiàn)爬蟲(chóng)程序示例分享
這篇文章主要介紹了使用webmagic實(shí)現(xiàn)爬蟲(chóng)程序示例,需要的朋友可以參考下2014-04-04