前兩天在工作中忙的焦頭爛額,涉及到@Transactional
對于事務的控制,便仔細研究了一下,頗有所獲,花費好了幾天測試整理,今天才發表出來,希望看到博客的老鐵們能有所獲吧。話不多說直奔正題。
先簡單介紹一下Spring事務的傳播行為:
所謂事務的傳播行為是指,如果在開始當前事務之前,一個事務上下文已經存在,此時有若干選項可以指定一個事務性方法的執行行為。在TransactionDefinition
定義中包括了如下幾個表示傳播行為的常量:
-
TransactionDefinition.PROPAGATION_REQUIRED
:如果當前存在事務,則加入該事務;如果當前沒有事務,則創建一個新的事務。這是默認值。 -
TransactionDefinition.PROPAGATION_REQUIRES_NEW
:創建一個新的事務,如果當前存在事務,則把當前事務掛起。 -
TransactionDefinition.PROPAGATION_SUPPORTS
:如果當前存在事務,則加入該事務;如果當前沒有事務,則以非事務的方式繼續運行。 -
TransactionDefinition.PROPAGATION_NOT_SUPPORTED
:以非事務方式運行,如果當前存在事務,則把當前事務掛起。 -
TransactionDefinition.PROPAGATION_NEVER
:以非事務方式運行,如果當前存在事務,則拋出異常。 -
TransactionDefinition.PROPAGATION_MANDATORY
:如果當前存在事務,則加入該事務;如果當前沒有事務,則拋出異常。 -
TransactionDefinition.PROPAGATION_NESTED
:如果當前存在事務,則創建一個事務作為當前事務的嵌套事務來運行;如果當前沒有事務,則該取值等價于TransactionDefinition.PROPAGATION_REQUIRED
。
然后說一下Spring事務的回滾機制:
Spring的AOP即聲明式事務管理默認是針對unchecked exception
回滾。Spring的事務邊界是在調用業務方法之前開始的,業務方法執行完畢之后來執行commit or rollback
(Spring默認取決于是否拋出runtimeException
)。
如果你在方法中有try{}catch(Exception e){}
處理,那么try里面的代碼塊就脫離了事務的管理,若要事務生效需要在catch中throw new RuntimeException ("xxxxxx");
這一點也是面試中會問到的事務失效的場景。
再簡單介紹一下@Transactional
注解底層實現方式吧,毫無疑問,是通過動態代理,那么動態代理又分為JDK自身和CGLIB,這個也不多贅述了,畢竟今天的主題是如何將@Transactional
對于事物的控制應用到爐火純青。哈哈~
第一點要注意的就是在@Transactional
注解的方法中,再調用本類中的其他方法method2時,那么method2方法上的@Transactional
注解是不!會!生!效!的!但是加上也并不會報錯,拿圖片簡單幫助理解一下吧。這一點也是面試中會問到的事務失效的場景。
通過代理對象在目標對象前后進行方法增強,也就是事務的開啟提交和回滾。那么繼續調用本類中其他方法是怎樣呢,如下圖:
可見目標對象內部的自我調用,也就是通過this.指向的目標對象將不會執行方法的增強。
先說第二點需要注意的地方,等下說如何解決上面第一點的問題。第二點就是@Transactional
注解的方法必須是公共方法,就是必須是public修飾符!!!
至于這個的原因,發表下個人的理解吧,因為JVM的動態代理是基于接口實現的,通過代理類將目標方法進行增強,想一下也是啦,沒有權限訪問那么你讓我怎么進行,,,好吧,這個我也沒有深入研究底層,個人理解個人理解。
在這里我也放個問題吧,希望有高手可以回復指點指點我,因為JVM動態代理是基于接口實現的,那么是不是service層都要按照接口和實現類的開發模式,注解才會生效呢,就是說controller
層直接調用沒有接口的service層,加了注解也一樣不起作用吧,這個懶了,沒有測試,其一是因為沒有人會這么開發吧,其二是我就認為是不起作用的,哈哈
下面來解決一下第一點的問題,如何在方法中調用本類中其他方法呢。
通過AopContext.currentProxy ()
獲取到本類的代理對象,再去調用就好啦。因為這個是CGLIB實現,所以要開啟AOP,當然也很簡單,在springboot啟動類上加上注解@EnableAspectJAutoProxy(exposeProxy = true)
就可以啦,這個依賴大家自行搜一下就好啦。要注意,注意,代理對象調用的方法也要是public修飾符,否則方法中獲取不到注入的bean,會報空指針錯誤。
emmmm,我先把調用的方式和結果說下吧。自己簡單寫了代碼,有點粗糙,就不要介意啦,嘿嘿。。。
Controller中調用Service
@RestController
publicclassTransactionalController{
@Autowired
privateTransactionalServicetransactionalService;
@PostMapping("transactionalTest")
publicvoidtransacionalTest(){
transactionalService.transactionalMethod();
}
}
Service中實現對事務的控制:接口
publicinterfaceTransactionalService{
voidtransactionalMethod();
}
Service中實現對事務的控制:實現類(各種情況的說明都寫在圖片里了,這樣方便閱讀,有助于快速理解吧)
上面兩種情況不管使不使用代理調用方法1和方法2,方法transactionalMethod
都處在一個事務中,四條更新操作全部失敗。
那么有人可能會有疑問了,在方法1和方法2上都加@Transactional
注解呢?答案是結果和上面是一致的。
小結只要方法transactionalMethod
上有注解,并且方法1和方法2都處于當前事務中(不使用代理調用,方法1和方法2上的@Transactional
注解是不生效的;使用代理,需要方法1和方法2都處在transactionalMethod
方法的事務中,默認或者嵌套事務均可,當然也可以不加@Transactional
注解),那么整體保持事務一致性。
如果想要方法1和方法2均單獨保持事務一致性怎么辦呢,剛說過了,如果不是用代理調用@Transactional
注解是不生效的,所以一定要使用代理調用實現,然后讓方法1和方法2分別單獨開啟新的事務,便OK啦。下面擺上圖片。
這兩種情況都是方法1和方法2均處在單獨的事務中,各自保持事務的一致性。
接下來進行進一步的優化,可以在transactionalMethod
方法中分別對方法1和方法2進行控制。要將代碼的藝術發揮到極致嘛,下面裝逼開始。
代碼太長了,超過屏幕了,粘貼出來截的圖,紅框注釋需要仔細看,希望不要影響你的閱讀體驗,至此,本篇關于@Transactioinal
注解的使用就到此為止啦,
簡單總結一下吧:
1、就是@Transactional
注解保證的是每個方法處在一個事務,如果有try一定在catch中拋出運行時異常。
2、方法必須是public修飾符。否則注解不會生效,但是加了注解也沒啥毛病,不會報錯,只是沒卵用而已。
3、this.本方法的調用,被調用方法上注解是不生效的,因為無法再次進行切面增強。
-
spring
+關注
關注
0文章
340瀏覽量
14343 -
注解
+關注
關注
0文章
18瀏覽量
2674
原文標題:如何將 @Transactional 事務注解運用到爐火純青?
文章出處:【微信號:AndroidPush,微信公眾號:Android編程精選】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論