0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

Spring中事務(wù)嵌套這么用一定得注意

jf_78858299 ? 來源:JAVA旭陽(yáng) ? 作者:JAVA旭陽(yáng) ? 2023-05-11 10:50 ? 次閱讀

前言

最近項(xiàng)目上有一個(gè)使用事務(wù)相對(duì)復(fù)雜的業(yè)務(wù)場(chǎng)景報(bào)錯(cuò)了。在絕大多數(shù)情況下,都是風(fēng)平浪靜,沒有問題。其實(shí)內(nèi)在暗流涌動(dòng),在有些異常情況下就會(huì)報(bào)錯(cuò),這種偶然性的問題很有可能就會(huì)在暴露到生產(chǎn)上造成事故,那究竟是怎么回事呢?

問題描述

我們用一個(gè)簡(jiǎn)單的例子模擬下,大家也可以看看下面這段代碼輸出的結(jié)果是什么。

  1. 在類SecondTransactionService定義一個(gè)簡(jiǎn)單接口transaction2,插入一個(gè)用戶,同時(shí)必然會(huì)拋出錯(cuò)誤
@Override
@Transactional(rollbackFor = Exception.class)
public void transaction2() {
    System.out.println("do transaction2.....");
    User user = new User("tx2", "111", 18);
    // 插入一個(gè)用戶
    userService.insertUser(user);
    // 跑錯(cuò)了
    throw new RuntimeException();
}
  1. 在另外一個(gè)類FirstTransactionService定義一個(gè)接口transaction1,它調(diào)用transaction2方法,同時(shí)做了try catch處理
@Override
@Transactional(rollbackFor = Exception.class)
public void transaction1() {
    System.out.println("do transaction1 .......");
    try {
        // 調(diào)用另外一個(gè)事務(wù),try catch住
        secondTransactionService.transaction2();
    } catch (Exception e) {
        e.printStackTrace();
    }

    // 插入當(dāng)前用戶tx1
    User user = new User("tx1", "111", 18);
    userService.insertUser(user);
}
  1. 定義一個(gè)controller,調(diào)用transaction1方法
@GetMapping("/testNestedTx")
public String testNestedTx() {
    firstTransactionService.transaction1();
    return "success";
}

大家覺得調(diào)用這個(gè)http接口,最終數(shù)據(jù)庫(kù)插入的是幾條數(shù)據(jù)呢?

問題結(jié)果

正確答案是數(shù)據(jù)庫(kù)插入了0條數(shù)據(jù)。

圖片

同時(shí)控制臺(tái)也報(bào)錯(cuò)了,報(bào)錯(cuò)原因是:org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only

圖片

是否和你預(yù)想的一樣呢?你知道是為什么嗎?

原因追溯

其實(shí)原因很簡(jiǎn)單,我們都知道,一個(gè)事務(wù)要么全成功提交事務(wù),要么失敗全部回滾。如果出現(xiàn)在一個(gè)事務(wù)中部分SQL要回滾,部分SQL要提交,這不就主打的一個(gè)”前后矛盾,精神分裂“嗎?

controller.testNestedTx() 
  || 
  / 
FirstTransactionService.transaction1()   REQUIRED隔離級(jí)別
       || 
       || 
       || 捕獲異常,提交事務(wù),出錯(cuò)啦
       / || 
FirstTransactionService.transaction2()   REQUIRED隔離級(jí)別
       || || 
       || 拋出異常,標(biāo)記事務(wù)為rollback only
       =======================
  1. 事務(wù)的隔離級(jí)別為REQUIRED,那么發(fā)現(xiàn)沒有事務(wù)開啟一個(gè)事務(wù)操作,有的話,就合并到這個(gè)事務(wù)中,所以transaction1()、transaction2()是在同一個(gè)事務(wù)中。
  2. transaction2()拋出異常,那么事務(wù)會(huì)被標(biāo)記為rollback only, 源碼如下所示:

圖片

  1. transaction1()由于try catch異常,正常運(yùn)行,想必就要可以提交事務(wù)了,在提交事務(wù)的時(shí)候,會(huì)檢查rollback標(biāo)記,如果是true, 這時(shí)候就會(huì)拋出上面的異常了。源碼如下圖所示:

圖片這下,是不是很清楚知道報(bào)錯(cuò)的原因了,那想想該怎么處理呢?

解決之道

知道了根本原因之后,是不是解決的方案就很明朗了,我們可以通過調(diào)整事務(wù)的傳播方式分拆多個(gè)事務(wù)管理,或者讓一個(gè)事務(wù)"前后一致",做一個(gè)誠(chéng)信的好事務(wù)。

  • try catch放到內(nèi)層事務(wù)中,也就是transaction2()方法中,這樣內(nèi)層事務(wù)會(huì)跟著外部事務(wù)進(jìn)行提交或者回滾。
@Override
    @Transactional(rollbackFor = Exception.class)
    public void transaction2() {
        try {
            System.out.println("do transaction2.....");
            User user = new User("tx2", "111", 18);
            userService.insertUser2(user);
            throw new RuntimeException();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
  • 如果希望內(nèi)層事務(wù)拋出異常時(shí)中斷程序執(zhí)行,直接在外層事務(wù)的catch代碼塊中拋出e,這樣同一個(gè)事務(wù)就都會(huì)回滾。
  • 如果希望內(nèi)層事務(wù)回滾,但不影響外層事務(wù)提交,需要將內(nèi)層事務(wù)的傳播方式指定為PROPAGATION_NESTED。PROPAGATION_NESTED基于數(shù)據(jù)庫(kù)savepoint實(shí)現(xiàn)的嵌套事務(wù),外層事務(wù)的提交和回滾能夠控制嵌內(nèi)層事務(wù),而內(nèi)層事務(wù)報(bào)錯(cuò)時(shí),可以返回原始savepoint,外層事務(wù)可以繼續(xù)提交。

圖片

事務(wù)的傳播機(jī)制

前面提到了事務(wù)的傳播機(jī)制,我們?cè)倏炊加心膸追N。

  • PROPAGATION_REQUIRED:加入到當(dāng)前事務(wù)中,如果當(dāng)前沒有事務(wù),就新建一個(gè)事務(wù)。這是最常見的選擇,也是Spring中默認(rèn)采用的方式。
  • PROPAGATION_SUPPORTS:支持當(dāng)前事務(wù),如果當(dāng)前沒有事務(wù),就以非事務(wù)方式執(zhí)行。
  • PROPAGATION_MANDATORY :支持當(dāng)前事務(wù),如果當(dāng)前沒有事務(wù),就拋出異常。
  • PROPAGATION_REQUIRES_NEW:新建一個(gè)事務(wù),如果當(dāng)前存在事務(wù),把當(dāng)前事務(wù)掛起。
  • PROPAGATION_NOT_SUPPORTED :以非事務(wù)方式執(zhí)行操作,如果當(dāng)前存在事務(wù),就把當(dāng)前事務(wù)掛起。
  • PROPAGATION_NEVER: 以非事務(wù)方式執(zhí)行,如果當(dāng)前存在事務(wù),則拋出異常。
  • PROPAGATION_NESTED :如果當(dāng)前存在事務(wù),則在嵌套事務(wù)內(nèi)執(zhí)行。如果當(dāng)前沒有事務(wù),則進(jìn)行與PROPAGATION_REQUIRED類似的操作。

如何理解PROPAGATION_NESTED的傳播機(jī)制呢,和PROPAGATION_REQUIRES_NEW又有什么區(qū)別呢?我們用一個(gè)例子說明白。

  • 定義serviceA.methodA()PROPAGATION_REQUIRED修飾;
  • 定義serviceB.methodB()以表格中三種方式修飾;
  • methodA中調(diào)用methodB;

圖片

總結(jié)

在我的項(xiàng)目中之所以會(huì)報(bào)“rollback-only”異常的根本原因是代碼風(fēng)格不一致的原因。外層事務(wù)對(duì)錯(cuò)誤的處理方式是返回true或false來告訴上游執(zhí)行結(jié)果,而內(nèi)層事務(wù)是通過拋出異常來告訴上游(這里指外層事務(wù))執(zhí)行結(jié)果,這種差異就導(dǎo)致了“rollback-only”異常。大家也可以去review自己項(xiàng)目中的代碼,是不是也偷偷犯下同樣的錯(cuò)誤了。

聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • 接口
    +關(guān)注

    關(guān)注

    33

    文章

    8625

    瀏覽量

    151341
  • 代碼
    +關(guān)注

    關(guān)注

    30

    文章

    4796

    瀏覽量

    68707
  • spring
    +關(guān)注

    關(guān)注

    0

    文章

    340

    瀏覽量

    14353
收藏 人收藏

    評(píng)論

    相關(guān)推薦

    Spring事務(wù)失效的十種常見場(chǎng)景

    Spring針對(duì)Java Transaction API (JTA)、JDBC、Hibernate和Java Persistence API(JPA)等事務(wù) API,實(shí)現(xiàn)了致的編程模型,而
    的頭像 發(fā)表于 12-11 15:03 ?922次閱讀

    Spring事務(wù)實(shí)現(xiàn)原理

    作者:京東零售 范錫軍 1、引言 springspring-tx模塊提供了對(duì)事務(wù)管理支持,使用spring事務(wù)可以讓我們從復(fù)雜的
    的頭像 發(fā)表于 11-08 10:10 ?837次閱讀
    <b class='flag-5'>Spring</b><b class='flag-5'>事務(wù)</b>實(shí)現(xiàn)原理

    什么是java spring

    。在SSH項(xiàng)目中管理事務(wù)以及對(duì)象的注入Spring是非侵入式的:基于Spring開發(fā)的系統(tǒng)的對(duì)象般不依賴于
    發(fā)表于 09-11 11:16

    Spring的兩種方式事務(wù)管理和API接口介紹

    Spring事務(wù)管理
    發(fā)表于 03-21 06:52

    Spring事務(wù)分析的實(shí)現(xiàn)方式

    Spring事務(wù)原理分析
    發(fā)表于 07-02 15:19

    詳解Spring事務(wù)管理

    在學(xué)習(xí)spring事務(wù)管理時(shí),我忍不住要問,spring為什么進(jìn)行事務(wù)管理,spring怎么進(jìn)行的事務(wù)
    發(fā)表于 07-12 06:54

    Spring事務(wù)管理詳解說明

    Spring事務(wù)管理詳解
    發(fā)表于 05-20 13:46

    spring聲明式事務(wù)實(shí)現(xiàn)原理猜想

    ? @Transactional注解簡(jiǎn)介 @Transactional 是spring聲明式事務(wù)管理的注解配置方式,相信這個(gè)注解的作用大家都很清楚。 @Transactional 注解可以幫助
    的頭像 發(fā)表于 10-13 09:20 ?1641次閱讀

    淺談Spring事務(wù)的那些坑

    對(duì)于從事java開發(fā)工作的同學(xué)來說,spring事務(wù)肯定再熟悉不過了。在某些業(yè)務(wù)場(chǎng)景下,如果同時(shí)有多張表的寫入操作,為了保證操作的原子性(要么同時(shí)成功,要么同時(shí)失敗)避免數(shù)據(jù)不致的情況,我們
    的頭像 發(fā)表于 10-11 10:31 ?756次閱讀

    發(fā)現(xiàn)個(gè)Spring事務(wù)的巨坑bug 你必須要小心了

    不正確 9.多線程調(diào)用 10.嵌套事務(wù)多回滾了 對(duì)于從事java開發(fā)工作的同學(xué)來說,spring事務(wù)肯定再熟悉不過了。在某些業(yè)務(wù)場(chǎng)景下,如果同時(shí)有多張表的寫入操作,為了保證操作的原子
    的頭像 發(fā)表于 10-11 18:17 ?866次閱讀

    淺談Spring事務(wù)底層原理

    開啟Spring事務(wù)本質(zhì)上就是增加了個(gè)Advisor,但我們使用@EnableTransactionManagement注解來開啟Spring事務(wù)
    的頭像 發(fā)表于 12-06 09:56 ?705次閱讀

    Spring事務(wù)在哪幾種情況下會(huì)不生效?

    日常開發(fā),我們經(jīng)常使用到spring事務(wù)。最近星球位還有去美團(tuán)面試,被問了這么道面試題:
    的頭像 發(fā)表于 05-10 17:53 ?941次閱讀
    <b class='flag-5'>Spring</b><b class='flag-5'>事務(wù)</b>在哪幾種情況下會(huì)不生效?

    8個(gè)Spring事務(wù)失效的場(chǎng)景介紹

    作為Java開發(fā)工程師,相信大家對(duì)Spring事務(wù)的使用并不陌生。但是你可能只是停留在基礎(chǔ)的使用層面上,在遇到些比較特殊的場(chǎng)景,事務(wù)可能沒有生效,直接在生產(chǎn)上暴露了,這可能就會(huì)導(dǎo)致
    的頭像 發(fā)表于 05-11 10:41 ?680次閱讀
    8個(gè)<b class='flag-5'>Spring</b><b class='flag-5'>事務(wù)</b>失效的場(chǎng)景介紹

    spring事務(wù)失效的些場(chǎng)景

    對(duì)于從事java開發(fā)工作的同學(xué)來說,spring事務(wù)肯定再熟悉不過了。 在某些業(yè)務(wù)場(chǎng)景下,如果個(gè)請(qǐng)求,需要同時(shí)寫入多張表的數(shù)據(jù)。為了保證操作的原子性(要么同時(shí)成功,要么同時(shí)失敗)
    的頭像 發(fā)表于 10-08 14:27 ?457次閱讀
    <b class='flag-5'>spring</b><b class='flag-5'>事務(wù)</b>失效的<b class='flag-5'>一</b>些場(chǎng)景

    Spring事務(wù)傳播性的相關(guān)知識(shí)

    本文主要介紹了Spring事務(wù)傳播性的相關(guān)知識(shí)。
    的頭像 發(fā)表于 01-10 09:29 ?463次閱讀
    <b class='flag-5'>Spring</b><b class='flag-5'>事務(wù)</b>傳播性的相關(guān)知識(shí)