小編給大家分享一下spring中@Transactional的失效場景有哪些,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!
Spring中對注解解析的尿性都是基于代理的,如果目標方法無法被Spring代理到,那么它將無法被Spring進行事務管理。
Spring生成代理的方式有兩種:
基于接口的JDK動態代理,要求目標代理類需要實現一個接口才能被代理
基于實現目標類子類的CGLIB代理
Spring在2.0之前,目標類如果實現了接口,則使用JDK動態代理方式,否則通過CGLIB子類的方式生成代理。
而在2.0版本之后,如果不在配置文件中顯示的指定spring.aop.proxy-tartget-class
的值,默認情況下生成代理的方式為CGLIB,如下圖
順著代理的思路,我們來看看哪些情況會因為代理不生效導致事務管控失敗。
(1)將注解標注在接口方法上
@Transactional
是支持標注在方法與類上的。一旦標注在接口上,對應接口實現類的代理方式如果是CGLIB,將通過生成子類的方式生成目標類的代理,將無法解析到@Transactional
,從而事務失效。
這種錯誤我們還是犯得比較少的,基本上我們都會將注解標注在接口的實現類方法上,官方也不推薦這種。
(2)被final、static關鍵字修飾的類或方法
CGLIB是通過生成目標類子類的方式生成代理類的,被final、static修飾后,無法繼承父類與父類的方法。
(3)類方法內部調用
事務的管理是通過代理執行的方式生效的,如果是方法內部調用,將不會走代理邏輯,也就調用不到了。
例如
在createUser中調用了內部方法createUser1,并且createUser1方法上設置了事務傳播策略為:REQUIRES_NEW,但是因為是內部直接調用,createUser1不能不代理處理,無法進行事務管理。在createUser1方法拋出異常后就插入數據失敗了。
但是這種操作在我們業務開發的過程中貌似還挺常見的,怎么樣才能保證其成功呢?
方式1:新建一個Service,將方法遷移過去,有點麻瓜。
方式2:在當前類注入自己,調用createUser1時通過注入的userService調用
方式3:通過AopContext.currentProxy()獲取代理對象
道理類似于方式2,就是為了通過代理來訪問內部方法
(4)當前類沒有被Spring管理
這個沒什么好說的,都沒有被Spring管理成為IOC容器中的一個bean,更別說被事務切面代理到了。
這種Bug看上去比較蠢,但沒準真的有人犯錯。
這類失效場景主要聚焦在框架本身在解析@Transactional
時的內部支持。如果使用的場景本身就是框架不支持的,那事務也是無法生效的。
(1)非public修飾的方法
我們在標有@Transactional
的任意方法上打個斷點,在idea內能看到事務切面點如下圖所示
點擊去這個方法,在開頭有這么一個調用
繼續進去
就能看到這么一句話了
不支持非public修飾的方法進行事務管理。
(2)多線程調用
跟上面一樣的的操作,我們能夠逐層進入到TransactionAspectSupport.prepareTransactionInfo
方法。
注意看以下這段話
從這里我們得知,事務信息是跟線程綁定的。
因此在多線程環境下,事務的信息都是獨立的,將會導致Spring在接管事務上出現差異。
這個場景我們要尤其注意!
給大家舉個例子
主線程A調用線程B保存Id為1的數據,然后主線程A等待線程B執行完成再通過線程A查詢id為1的數據。
這時你會發現在主線程A中無法查詢到id為1的數據。因為這兩個線程在不同的Spring事務中,本質上會導致它們在Mysql中存在不同的事務中。
Mysql中通過MVCC保證了線程在快照讀時只讀取小于當前事務號的數據,在線程B顯然事務號是大于線程A的,因此查詢不到數據。
(3)數據庫本身不支持事務
比如Mysql的Myisam存儲引擎是不支持事務的,只有innodb存儲引擎才支持。
這個問題出現的概率極其小,因為Mysql5之后默認情況下是使用innodb存儲引擎了。
但如果配置錯誤或者是歷史項目,發現事務怎么配都不生效的時候,記得看看存儲引擎本身是否支持事務。
(4)未開啟事務
這個也是一個比較麻瓜的問題,在Springboot項目中已經不存在了,已經有DataSourceTransactionManagerAutoConfiguration默認開啟了事務管理。
但是在MVC項目中還需要在applicationContext.xml文件中,手動配置事務相關參數。如果忘了配置,事務肯定是不會生效的。
注意啦注意啦,下面這幾種都是高頻會出現的Bug!
(1)錯誤的傳播機制
Spring支持了7種傳播機制,分別為:
上面不支持事務的傳播機制為:PROPAGATION_SUPPORTS,PROPAGATION_NOT_SUPPORTED,PROPAGATION_NEVER。
如果配置了這三種傳播方式的話,在發生異常的時候,事務是不會回滾的。
(2)rollbackFor屬性設置錯誤
默認情況下事務僅回滾運行時異常和Error,不回滾受檢異常(例如IOException)。
因此如果方法中拋出了IO異常,默認情況下事務也會回滾失敗。
我們可以通過指定@Transactional(rollbackFor = Exception.class)
的方式進行全異常捕獲。
(3)異常被內部catch
UserService
UserService1
如上代碼UserService調用了UserService1中的方法,并且捕獲了UserService1中拋出的異常。
你將能看到控制臺出現這樣一個報錯:
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
默認情況下標注了@Transactional
注解的方法的事務傳播機制是REQUIRED,它的特性是支持當前事務,也就說加入當前事務。我們在UserService中開始事務,然后再UserService1中拋出異?;貪LUserService中的事務,將其標記為只讀。
但是在UserSevice中我們捕獲了異常,此時UserService上的事務認為正常提交事務。最后在提交時發現事務只讀,已經被回滾,則拋出了上述異常。
因此這里如果需要對特定的異常進行捕獲處理,記得再次將異常拋出,讓最外層的事務感知到。
(4)嵌套事務
上面是我想同時回滾UserService與UserService1。但是也會有這種場景只想回滾UserService1中報錯的數據庫操作,不影響主邏輯UserService中的數據落庫。
有兩種方式可以實現上述邏輯:
1.直接在UserService1內的整個方法用try/catch包住
2.在UserService1使用Propagation.REQUIRES_NEW傳播機制
本文為大家分析@Transactional
注解使用過程中失效的12種場景
最后,@Transactional
注解雖香,但是復雜業務邏輯下,為了更好的管理事務與把控業務處理時事務的細粒度,我還是推薦大家使用編程式事務。
以上是“spring中@Transactional的失效場景有哪些”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。