如何將 -Transactional 事務註解運用到爐火純青?

前兩天在工作中忙的焦頭爛額,涉及到@Transactional對於事務的控制,便仔細研究了一下,頗有所獲,花費好了幾天測試整理,今天才發表出來,希望看到博客的老鐵們能有所獲吧。話不多說直奔正題。

先簡單介紹一下 Spring 事務的傳播行爲:

所謂事務的傳播行爲是指,如果在開始當前事務之前,一個事務上下文已經存在,此時有若干選項可以指定一個事務性方法的執行行爲。在TransactionDefinition定義中包括瞭如下幾個表示傳播行爲的常量:

然後說一下 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
public class TransactionalController {
 
    @Autowired
    private TransactionalService transactionalService;
 
    @PostMapping("transactionalTest")
    public void transacionalTest(){
        transactionalService.transactionalMethod();
    }
}

Service 中實現對事務的控制:接口

public interface TransactionalService {
    void transactionalMethod();
}

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. 本方法的調用,被調用方法上註解是不生效的,因爲無法再次進行切面增強。

如果有更細緻的討論歡迎評論,感謝閱讀。

來源:blog.csdn.net/fanxb92/article/details/81296005

碼農 code 之路 專注原創,Java 後端,大數據,架構設計,消息隊列,Python 技術,面試題,數據結構與算法,職場經驗分享,致力打造一個有營養的公衆號。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/1XLPix-PKF87t-J7Gxst9Q