javascript
Spring 事务方法与非事务方法相互调用 @Transactional 注解失效不回滚?
寫這篇文章的初衷呢就是最近遇到了一個Spring事務的大坑。與其說是坑,還不如說是自己事務這塊兒太薄弱導致的(自嘲下)。
項目環境 Spring Boot
下面開始問題描述,發生的過程有點長,想直接看方案的直接跳過哦~!
最近在做項目中有個業務是每天定時更新xx的數據,某條記錄更新中數據出錯,不影響整體數據,只需記錄下來并回滾當條記錄所關聯的表數據;好啊!
這個簡單,接到任務后,樓主我三下五除二就寫完了,由于這個業務還是有些麻煩,我就在一個service里拆成了兩個方法去執行,一個方法(A) 是查詢數據與驗證組裝數據。
推薦后臺管理開源框架,基于 Spring Boot、Spring Security、JWT 后端框架,Vue & Element 前端框架的前后端分離的用戶權限管理系統,代碼易讀易懂、界面簡潔美觀。其核心技術采用 Spring、MyBatis、Shiro 沒有任何其它過度依賴包,下載即可運行使用。
另外一個方法(B)更新這條數據所對應的表(執行的時候是方法A中調用方法B);由于這個數據是循環更新,所以我想的是,一條數據更新失敗直接回滾此條數據就是,不會影響其他數據,其他的照常更新,所以我就在方法B上加了事務,方法A沒有加;以為很完美,自測一下正常。
ok通過,再測試一下報錯情況,是否回滾,一測沒回滾,懵圈兒?以為代碼寫錯了,改了幾處地方,再測了幾次,均沒回滾。這下是真難受了。
好啦,寫到這里相信各位看官心里肯定在嘲諷老弟了,spring的傳播機制都沒搞明白(難受)。
下面開始一步步分析解決問題:
首先我們來看下spring事務的傳播機制及原因分析;
PROPAGATION_REQUIRED – 支持當前事務,如果當前沒有事務,就新建一個事務。
這是最常見的選擇。
PROPAGATION_SUPPORTS – 支持當前事務,如果當前沒有事務,就以非事務方式執行。
PROPAGATION_MANDATORY – 支持當前事務,如果當前沒有事務,就拋出異常。
PROPAGATION_REQUIRES_NEW – 新建事務,如果當前存在事務,把當前事務掛起。
PROPAGATION_NOT_SUPPORTED – 以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。
PROPAGATION_NEVER – 以非事務方式執行,如果當前存在事務,則拋出異常。
PROPAGATION_NESTED – 如果當前存在事務,則在嵌套事務內執行。
如果當前沒有事務,則進行與PROPAGATION_REQUIRED類似的操作。
spring默認的是PROPAGATION_REQUIRED機制,如果方法A標注了注解@Transactional** 是完全沒問題的,執行的時候傳播給方法B**,因為方法A開啟了事務,線程內的connection的屬性autoCommit=false,并且執行到方法B時。事務傳播依然是生效的,得到的還是方法A**的connection,autoCommit還是為false,所以事務生效;
反之,如果方法A沒有注解**@Transactional** 時是不受事務管理的,autoCommit=true,那么傳播給方法B的也為true,執行完自動提交,即使B標注了**@Transactional ;**
在一個Service內部,事務方法之間的嵌套調用,普通方法和事務方法之間的嵌套調用,都不會開啟新的事務。是因為spring采用動態代理機制來實現事務控制,而動態代理最終都是要調用原始對象的,而原始對象在去調用方法時,是不會再觸發代理了!
所以以上就是為什么我在沒有標注事務注解的方法A里去調用標注有事務注解的方法B而沒有事務滾回的原因。
看到這里,有的看官可能在想你在方法A上標個注解不就完了嗎?為什么非要標注在方法B上?
由于我這里是循環更新數據,調用一次方法B就更新一次數據,涉及到幾張表,需要執行幾條update sql,一條數據更新失敗不影響所有數據,所以說一條數據更新執行完畢后就提交一次事務,如果標注在方法A上,要所有的都執行完畢了才提交事務,這樣子是有問題滴。
下邊先上下代碼:
方法A:無事務控制
方法B:有事務控制
方法B處理失敗手動拋出異常觸發回滾:
方法A調用方法B:
從上圖可以看到,如果方法B中User更新出錯后需要回滾RedPacket數據,所以User更新失敗就拋出了繼承自RuntimeException的自定義異常,并且在調用方把這個異常catch到重新拋出,觸發事務回滾,但是并沒有執行;
解決方案
1、把方法B抽離到另外一個XXService中去,并且在這個Service中注入XXService,使用XXService調用方法B。
顯然,這種方式一點也不優雅,且要產生很多冗余文件,看起來很煩,實際開發中也幾乎沒人這么做吧?反正我不建議采用此方案;
2、通過在方法內部獲得當前類代理對象的方式,通過代理對象調用方法B
上面說了:動態代理最終都是要調用原始對象的,而原始對象在去調用方法時,是不會再觸發代理了!
所以我們就使用代理對象來調用,就會觸發事務;
綜上解決方案,我覺得第二種方式簡直方便到炸,那怎么獲取代理對象呢?這里提供兩種方式:
1、使用 ApplicationContext 上下文對象獲取該對象;
2、使用 AopContext.currentProxy() 獲取代理對象,但是需要配置exposeProxy=true
我這里使用的是第二種解決方案,具體操作如下:
springboot啟動類加上注解:@EnableAspectJAutoProxy(exposeProxy = true)
方法內部獲取代理對象調用方法
完了后再測試,數據順利回滾,至此,問題得到解決!
都是事務這塊兒基礎太差的錯啊~~希望各位遇到這種問題的兄弟些都好好的去研究研究spring這塊兒,好了不說了,我也該去深造了!
總結
以上是生活随笔為你收集整理的Spring 事务方法与非事务方法相互调用 @Transactional 注解失效不回滚?的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: rust实战入门到进阶(4)
- 下一篇: c++17进阶(2)-Lua扩展(1)