@Transaction必知必會(huì)

1. Spring事務(wù)的基本原理

事務(wù)管理是應(yīng)用系統(tǒng)開(kāi)發(fā)中必不可少的一部分。Spring 為事務(wù)管理提供了豐富的功能支持。Spring 事務(wù)管理分為編碼式和聲明式的兩種方式。編程式事務(wù)指的是通過(guò)編碼方式實(shí)現(xiàn)事務(wù);聲明式事務(wù)基于 AOP,將具體業(yè)務(wù)邏輯與事務(wù)處理解耦。聲明式事務(wù)管理使業(yè)務(wù)代碼邏輯不受污染, 因此在實(shí)際使用中聲明式事務(wù)用的比較多。聲明式事務(wù)有兩種方式,一種是在配置文件(xml)中做相關(guān)的事務(wù)規(guī)則聲明,另一種是基于@Transactional 注解的方式。注釋配置是目前流行的使用方式,因此本文將著重介紹基于@Transactional 注解的事務(wù)管理。
使用@Transactional的相比傳統(tǒng)的我們需要手動(dòng)開(kāi)啟事務(wù),然后提交事務(wù)來(lái)說(shuō)。它提供如下方便

  • 根據(jù)你的配置,設(shè)置是否自動(dòng)開(kāi)啟事務(wù)
  • 自動(dòng)提交事務(wù)或者遇到異常自動(dòng)回滾

聲明式事務(wù)(@Transactional)基本原理如下:

  1. 配置文件開(kāi)啟注解驅(qū)動(dòng),在相關(guān)的類和方法上通過(guò)注解@Transactional標(biāo)識(shí)。
  2. spring 在啟動(dòng)的時(shí)候會(huì)去解析生成相關(guān)的bean,這時(shí)候會(huì)查看擁有相關(guān)注解的類和方法,并且為這些類和方法生成代理,并根據(jù)@Transaction的相關(guān)參數(shù)進(jìn)行相關(guān)配置注入,這樣就在代理中為我們把相關(guān)的事務(wù)處理掉了(開(kāi)啟正常提交事務(wù),異?;貪L事務(wù))。
  3. 真正的數(shù)據(jù)庫(kù)層的事務(wù)提交和回滾是通過(guò)binlog或者redo log實(shí)現(xiàn)的。
2. @Transactional基本配置解析
@Transactional
    public void saveUser(){

        User user = new User();
        user.setAge(22);
        user.setName("maskwang");
        logger.info("save the user{}",user);
        userRepository.save(user);
    }

如上面這個(gè)例子一樣,很輕松的就能應(yīng)用事務(wù)。只需要在方法上加入@Transactional注解。當(dāng)@Transactional加在方法上,表示對(duì)該方法應(yīng)用事務(wù)。當(dāng)加在類上,表示對(duì)該類里面所有的方法都應(yīng)用相同配置的事務(wù)。接下來(lái)對(duì)@Transactional的參數(shù)解析。

public @interface Transactional {
    @AliasFor("transactionManager")
    String value() default "";

    @AliasFor("value")
    String transactionManager() default "";

    Propagation propagation() default Propagation.REQUIRED;

    Isolation isolation() default Isolation.DEFAULT;

    int timeout() default -1;

    boolean readOnly() default false;

    Class<? extends Throwable>[] rollbackFor() default {};

    String[] rollbackForClassName() default {};

    Class<? extends Throwable>[] noRollbackFor() default {};

    String[] noRollbackForClassName() default {};
}
  • transactionManager()表示應(yīng)用那個(gè)應(yīng)用那個(gè)TransactionManager.常用的有如下的事務(wù)管理器
image.png
  • isolation()表示隔離級(jí)別
image.png

臟讀:一事務(wù)對(duì)數(shù)據(jù)進(jìn)行了增刪改,但未提交,另一事務(wù)可以讀取到未提交的數(shù)據(jù)。如果第一個(gè)事務(wù)這時(shí)候回滾了,那么第二個(gè)事務(wù)就讀到了臟數(shù)據(jù)。
不可重復(fù)讀:一個(gè)事務(wù)中發(fā)生了兩次讀操作,第一次讀操作和第二次操作之間,另外一個(gè)事務(wù)對(duì)數(shù)據(jù)進(jìn)行了修改,這時(shí)候兩次讀取的數(shù)據(jù)是不一致的。
幻讀:第一個(gè)事務(wù)對(duì)一定范圍的數(shù)據(jù)進(jìn)行批量修改,第二個(gè)事務(wù)在這個(gè)范圍增加一條數(shù)據(jù),這時(shí)候第一個(gè)事務(wù)就會(huì)丟失對(duì)新增數(shù)據(jù)的修改。

不可重復(fù)讀的重點(diǎn)是修改 :同樣的條件, 你讀取過(guò)的數(shù)據(jù),再次讀取出來(lái)發(fā)現(xiàn)值不一樣了幻讀的重點(diǎn)在于新增或者刪除:同樣的條件, 第 1 次和第 2 次讀出來(lái)的記錄數(shù)不一樣

  • propagation()表示事務(wù)的傳播屬性
    事務(wù)的傳播是指事務(wù)的嵌套的時(shí)候,它們的事務(wù)屬性。常見(jiàn)的傳播屬性有如下幾個(gè)
  1. PROPAGATION_REQUIRED(spring 默認(rèn))
    假設(shè)外層事務(wù) Service A 的 Method A() 調(diào)用 內(nèi)層Service B 的 Method B()。如果ServiceB.methodB() 的事務(wù)級(jí)別定義為 PROPAGATION_REQUIRED,那么執(zhí)行 ServiceA.methodA() 的時(shí)候spring已經(jīng)起了事務(wù),這時(shí)調(diào)用 ServiceB.methodB(),ServiceB.methodB() 看到自己已經(jīng)運(yùn)行在 ServiceA.methodA() 的事務(wù)內(nèi)部,就不再起新的事務(wù)。假如 ServiceB.methodB() 運(yùn)行的時(shí)候發(fā)現(xiàn)自己沒(méi)有在事務(wù)中,他就會(huì)為自己分配一個(gè)事務(wù)。不管如何,ServiceB.methodB()都會(huì)在事務(wù)中。

  2. PROPAGATION_REQUIRES_NEW
    比如我們?cè)O(shè)計(jì) ServiceA.methodA() 的事務(wù)級(jí)別為 PROPAGATION_REQUIRED,ServiceB.methodB() 的事務(wù)級(jí)別為 PROPAGATION_REQUIRES_NEW。那么當(dāng)執(zhí)行到 ServiceB.methodB() 的時(shí)候,ServiceA.methodA() 所在的事務(wù)就會(huì)掛起,ServiceB.methodB() 會(huì)起一個(gè)新的事務(wù),等待 ServiceB.methodB() 的事務(wù)完成以后,它才繼續(xù)執(zhí)行。它與1中的區(qū)別在于ServiceB.methodB() 新起了一個(gè)事務(wù)。如過(guò)ServiceA.methodA() 發(fā)生異常,ServiceB.methodB() 已經(jīng)提交的事務(wù)是不會(huì)回滾的。

  3. PROPAGATION_SUPPORTS
    假設(shè)ServiceB.methodB() 的事務(wù)級(jí)別為 PROPAGATION_SUPPORTS,那么當(dāng)執(zhí)行到ServiceB.methodB()時(shí),如果發(fā)現(xiàn)ServiceA.methodA()已經(jīng)開(kāi)啟了一個(gè)事務(wù),則加入當(dāng)前的事務(wù),如果發(fā)現(xiàn)ServiceA.methodA()沒(méi)有開(kāi)啟事務(wù),則自己也不開(kāi)啟事務(wù)。這種時(shí)候,內(nèi)部方法的事務(wù)性完全依賴于最外層的事務(wù)。
    剩下幾種就不多介紹,可以參考這篇文章。http://www.itdecent.cn/p/249f2cd42692

  • readOnly() 事務(wù)超時(shí)設(shè)置.超過(guò)這個(gè)時(shí)間,發(fā)生回滾

  • readOnly() 只讀事務(wù)
    從這一點(diǎn)設(shè)置的時(shí)間點(diǎn)開(kāi)始(時(shí)間點(diǎn)a)到這個(gè)事務(wù)結(jié)束的過(guò)程中,其他事務(wù)所提交的數(shù)據(jù),該事務(wù)將看不見(jiàn)?。ú樵冎胁粫?huì)出現(xiàn)別人在時(shí)間點(diǎn)a之后提交的數(shù)據(jù))。
    注意是一次執(zhí)行多次查詢來(lái)統(tǒng)計(jì)某些信息,這時(shí)為了保證數(shù)據(jù)整體的一致性,要用只讀事務(wù)

  • rollbackFor()導(dǎo)致事務(wù)回滾的異常類數(shù)組.

  • rollbackForClassName() 導(dǎo)致事務(wù)回滾的異常類名字?jǐn)?shù)組

  • noRollbackFor 不會(huì)導(dǎo)致事務(wù)回滾的異常類數(shù)組

  • noRollbackForClassName 不會(huì)導(dǎo)致事務(wù)回滾的異常類名字?jǐn)?shù)組

3. @Transactional 使用應(yīng)該注意的地方
3.1 默認(rèn)情況下,如果在事務(wù)中拋出了未檢查異常(繼承自 RuntimeException 的異常)或者 Error,則 Spring 將回滾事務(wù);除此之外,Spring 不會(huì)回滾事務(wù)。你如果想要在特定的異?;貪L可以考慮rollbackFor()等屬性
3.2 @Transactional 只能應(yīng)用到 public 方法才有效。

這是因?yàn)樵谑褂?Spring AOP 代理時(shí),Spring 會(huì)調(diào)用 TransactionInterceptor在目標(biāo)方法執(zhí)行前后進(jìn)行攔截之前,DynamicAdvisedInterceptorCglibAopProxy的內(nèi)部類)的的 intercept方法或 JdkDynamicAopProxy 的 invoke 方法會(huì)間接調(diào)用 AbstractFallbackTransactionAttributeSource(Spring 通過(guò)這個(gè)類獲取@Transactional 注解的事務(wù)屬性配置屬性信息)的 computeTransactionAttribute 方法。

@Nullable
    protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
       //這里判斷是否是public方法
        if(this.allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
            return null;
        } 
//省略其他代碼

若不是 public,就不會(huì)獲取@Transactional 的屬性配置信息,最終會(huì)造成不會(huì)用 TransactionInterceptor 來(lái)攔截該目標(biāo)方法進(jìn)行事務(wù)管理。整個(gè)事務(wù)執(zhí)行的時(shí)序圖如下。


image.png
3.3 Spring 的 AOP 的自調(diào)用問(wèn)題

在 Spring 的 AOP 代理下,只有目標(biāo)方法由外部調(diào)用,目標(biāo)方法才由 Spring 生成的代理對(duì)象來(lái)管理,這會(huì)造成自調(diào)用問(wèn)題。若同一類中的其他沒(méi)有@Transactional注解的方法內(nèi)部調(diào)用有@Transactional注解的方法,有@Transactional注解的方法的事務(wù)被忽略,不會(huì)發(fā)生回滾。這個(gè)問(wèn)題是由于Spring AOP 代理造成的(如下面代碼所示)。之所以沒(méi)有應(yīng)用事務(wù),是因?yàn)樵趦?nèi)部調(diào)用,而代理后的類(把目標(biāo)類作為成員變量靜態(tài)代理)只是調(diào)用成員變量中的對(duì)應(yīng)方法,自然也就沒(méi)有aop中的advice,造成只能調(diào)用父類的方法。另外一個(gè)問(wèn)題是只能應(yīng)用在public方法上。為解決這兩個(gè)問(wèn)題,使用 AspectJ 取代 Spring AOP 代理。

@Transactional
 public void saveUser(){
        User user = new User();
        user.setAge(22);
        user.setName("mask");
        logger.info("save the user{}",user);
        userRepository.save(user);
       // throw new RuntimeException("exception");
    }
 public void saveUserBack(){
        saveUser();   //自調(diào)用發(fā)生
    }
3.4 自注入解決辦法
@Service
public class UserService {

    Logger logger = LoggerFactory.getLogger(UserService.class);

    @Autowired
    UserRepository userRepository;
    
    @Autowired 
    UserService userService; //自注入來(lái)解決

    @Transactional
    public void saveUser(){

        User user = new User();
        user.setAge(22);
        user.setName("mask");
        logger.info("save the user{}",user);
        userRepository.save(user);
       // throw new RuntimeException("exception");
    }
 public void saveUserBack(){
        saveUser();
    }
}

另外也可以把注解加到類上來(lái)解決。

4. 總結(jié)

@Transactional用起來(lái)是方便,但是我們需要明白它背后的原理,避免入坑。另外@Transactional不建議用在處理時(shí)間過(guò)長(zhǎng)的事務(wù)。因?yàn)?,它?huì)一直持有數(shù)據(jù)庫(kù)線程池的連接,造成不能及時(shí)返回。就是盡量是的事務(wù)的處理時(shí)間短。

參考文章:

Spring @Transactional的使用及原理
深入理解 Spring 事務(wù)原理
透徹的掌握 Spring 中@transactional 的使用
在同一個(gè)類中,一個(gè)方法調(diào)用另外一個(gè)有注解(比如@Async,@Transational)的方法,注解失效的原因和解決方法

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 很多人喜歡這篇文章,特此同步過(guò)來(lái) 由淺入深談?wù)搒pring事務(wù) 前言 這篇其實(shí)也要?dú)w納到《常識(shí)》系列中,但這重點(diǎn)又...
    碼農(nóng)戲碼閱讀 4,904評(píng)論 2 59
  • 一、事務(wù)的基本原理Spring事務(wù)的本質(zhì)其實(shí)就是數(shù)據(jù)庫(kù)對(duì)事務(wù)的支持,沒(méi)有數(shù)據(jù)庫(kù)的事務(wù)支持,spring是無(wú)法提供事...
    阿燈_supwinr閱讀 14,833評(píng)論 2 28
  • 用時(shí):65分鐘 注意細(xì)節(jié) 用時(shí):50分鐘 身體畫的有點(diǎn)高,比例的問(wèn)題感覺(jué)整體沒(méi)有萌萌的感覺(jué)
    慕子昱閱讀 300評(píng)論 0 0
  • 露珠在夜里凝聚身體 只為 灌溉黎明 明月在夜里劃過(guò)長(zhǎng)空 只為 尋找倒影 紅花在夜里墜入泥土 只為 祭奠愛(ài)情
    利哥街訪閱讀 311評(píng)論 1 3
  • 昨天…… 今天我興高采烈的來(lái)學(xué)校。坐到自己的座位上左顧右盼。忽然,我看見(jiàn)范祥的臉色不太好:他的表情看起來(lái)有點(diǎn)兒難受...
    顏丙鈺閱讀 272評(píng)論 0 0

友情鏈接更多精彩內(nèi)容