<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Spring 聲明式事務(wù)應(yīng)該怎么學(xué)?

          共 16601字,需瀏覽 34分鐘

           ·

          2021-09-22 21:34

          點(diǎn)擊關(guān)注公眾號(hào),Java干貨及時(shí)送達(dá)

          1、引言

          Spring 的聲明式事務(wù)極大地方便了日常的事務(wù)相關(guān)代碼編寫(xiě),它的設(shè)計(jì)如此巧妙,以至于在使用中幾乎感覺(jué)不到它的存在,只需要優(yōu)雅地加一個(gè) @Transactional 注解,一切就都順理成章地完成了!

          毫不夸張地講,Spring 的聲明式事務(wù)實(shí)在是太好用了,以至于大多數(shù)人都忘記了編程式事務(wù)應(yīng)該怎么寫(xiě)。

          不過(guò),越是你認(rèn)為理所應(yīng)當(dāng)?shù)氖虑椋绻隽藛?wèn)題,就越難排查。不知道你和身邊的小伙伴有沒(méi)有遇到過(guò) @Transactional 失效的場(chǎng)景,這不但是日常開(kāi)發(fā)中常踩的坑,也是面試中的高頻問(wèn)題。

          其實(shí)這些失效場(chǎng)景不用死記硬背,如果搞明白了它的工作原理,再結(jié)合源碼,需要用到的時(shí)候 Debug 一下就能自己分析出來(lái)。畢竟,源碼才是最好的說(shuō)明書(shū)。

          還是那句話,授人以魚(yú)不如授人以漁,課代表就算總結(jié) 100 種失效場(chǎng)景,也不一定能覆蓋到你可能踩到的坑。所以本文中,課代表將結(jié)合幾個(gè)常見(jiàn)失效情況,從源碼層面解釋其失效原因。認(rèn)真讀完本文,相信你會(huì)對(duì)聲明式事務(wù)有更深刻的認(rèn)識(shí)。

          2、回顧手寫(xiě)事務(wù)

          數(shù)據(jù)庫(kù)層面的事務(wù),有 ACID 四個(gè)特性,他們共同保證了數(shù)據(jù)庫(kù)中數(shù)據(jù)的準(zhǔn)確性。事務(wù)的原理并不是本文的重點(diǎn),我們只需要知道樣例中用的 H2 數(shù)據(jù)庫(kù)完全實(shí)現(xiàn)了對(duì)事務(wù)的支持(read committed)。

          編寫(xiě) Java 代碼時(shí),我們使用 JDBC 接口與數(shù)據(jù)庫(kù)交互,完成事務(wù)的相關(guān)指令,偽代碼如下:

          //獲取用于和數(shù)據(jù)庫(kù)交互的連接
          Connection conn = DriverManager.getConnection();
          try {
              // 關(guān)閉自動(dòng)提交:
              conn.setAutoCommit(false);
              // 執(zhí)行多條SQL語(yǔ)句:
              insert(); 
              update(); 
              delete();
              // 提交事務(wù):
              conn.commit();
          catch (SQLException e) {
              // 如果出現(xiàn)異常,回滾事務(wù):
              conn.rollback();
          finally {
              //釋放資源
              conn.close();
          }

          這是典型的編程式事務(wù)代碼流程:開(kāi)始前先關(guān)閉自動(dòng)提交,因?yàn)槟J(rèn)情況下,自動(dòng)提交是開(kāi)啟的,每條語(yǔ)句都會(huì)開(kāi)啟新事務(wù),執(zhí)行完畢后自動(dòng)提交。

          關(guān)閉事務(wù)的自動(dòng)提交,是為了讓多個(gè) SQL 語(yǔ)句在同一個(gè)事務(wù)中。代碼正常運(yùn)行,就提交事務(wù),出現(xiàn)異常,就整體回滾,以此保證多條 SQL 語(yǔ)句的整體性。

          除了事務(wù)提交,數(shù)據(jù)庫(kù)還支持保存點(diǎn)的概念,在一個(gè)物理事務(wù)中,可以設(shè)置多個(gè)保存點(diǎn),方便回滾到指定保存點(diǎn)(其類似玩單機(jī)游戲時(shí)的存檔,你可以在角色掛掉后隨時(shí)回到上次的存檔)設(shè)置和回滾到保存點(diǎn)的代碼如下:

          //設(shè)置保存點(diǎn)    
          Savepoint savepoint = connection.setSavepoint();
          //回滾到指定的保存點(diǎn)
          connection.rollback(savepoint);
          //回滾到保存點(diǎn)后按需提交/回滾前面的事務(wù)
          conn.commit();//conn.rollback();

          Spring 聲明式事務(wù)所做的工作,就是圍繞著 提交/回滾 事務(wù),設(shè)置/回滾到保存點(diǎn) 這兩對(duì)命令進(jìn)行的。為了讓我們盡可能地少寫(xiě)代碼,Spring 定義了幾種傳播屬性將事務(wù)做了進(jìn)一步的抽象。

          注意哦,Spring 的事務(wù)傳播(Propagation) 只是 Spring 定義的一層抽象而已,和數(shù)據(jù)庫(kù)沒(méi)啥關(guān)系,不要和數(shù)據(jù)庫(kù)的事務(wù)隔離級(jí)別混淆。

          3、Spring 的事務(wù)傳播(Transaction Propagation)

          觀察傳統(tǒng)事務(wù)代碼:

           conn.setAutoCommit(false);
              // 執(zhí)行多條SQL語(yǔ)句:
              insert(); 
              update(); 
              delete();
              // 提交事務(wù):
              conn.commit();

          這段代碼表達(dá)的是三個(gè) SQL 語(yǔ)句在同一個(gè)事務(wù)里。

          他們可能是同一個(gè)類中的不同方法,也可能是不同類中的不同方法。如何來(lái)表達(dá)諸如事務(wù)方法加入別的事務(wù)、新建自己的事務(wù)、嵌套事務(wù)等等概念呢?這就要靠 Spring 的事務(wù)傳播機(jī)制了。

          事務(wù)傳播(Transaction Propagation)就是字面意思:事務(wù)的傳播/傳遞 方式。

          在 Spring 源碼的TransactionDefinition接口中,定義了 7 種傳播屬性,官網(wǎng)對(duì)其中的 3 個(gè)做了說(shuō)明,我們只要搞懂了這 3 個(gè),剩下的 4 個(gè)就是舉一反三的事了。

          面試官:@Transactional 注解是如何實(shí)現(xiàn)的?面試必問(wèn)!

          1)PROPAGATION_REQUIRED

          字面意思:傳播-必須

          PROPAGATION_REQUIRED是其默認(rèn)傳播屬性,強(qiáng)制開(kāi)啟事務(wù),如果之前的方法已經(jīng)開(kāi)啟了事務(wù),則加入前一個(gè)事務(wù),二者在物理上屬于同一個(gè)事務(wù)。

          一圖勝千言,下圖表示它倆物理上是在同一個(gè)事務(wù)內(nèi):

          上圖翻譯成偽代碼是這樣的:

          try {
              conn.setAutoCommit(false);
              transactionalMethod1(); 
              transactionalMethod2();
              conn.commit();
          catch (SQLException e) {
              conn.rollback();
          finally {
              conn.close();
          }

          既然在同一個(gè)物理事務(wù)中,那如果transactionalMethod2()發(fā)生了異常,導(dǎo)致需要回滾,那么請(qǐng)問(wèn)transactionalMethod1()是否也要回滾呢?

          得益于上面的圖解和偽代碼,我們可以很容易地得出答案,transactionalMethod1()肯定回滾了。另外,Spring 系列面試題和答案全部整理好了,微信搜索Java技術(shù)棧,在后臺(tái)發(fā)送:面試,可以在線閱讀。

          這里拋一個(gè)問(wèn)題:

          事務(wù)方法里面的異常被 try catch 吃了,事務(wù)還能回滾嗎?

          先別著急出結(jié)論, 看下面兩段代碼示例。

          示例一:不會(huì)回滾的情況(事務(wù)失效)

          觀察下面的代碼,methodThrowsException()什么也沒(méi)干,就拋了個(gè)異常,調(diào)用方將其拋出的異常try catch 住了,該場(chǎng)景下是不會(huì)觸發(fā)回滾的

          @Transactional(rollbackFor = Exception.class)
          public void tryCatchRollBackFail(String name
          {
              jdbcTemplate.execute("INSERT INTO USER (NAME) VALUES ('" + name + "')");
              try {
                  methodThrowsException();
              } catch (RollBackException e) {
                  //do nothing
              }
          }

          public void methodThrowsException() throws RollBackException {
              throw new RollBackException(ROLL_BACK_MESSAGE);
          }


          示例二:會(huì)回滾的情況(事務(wù)生效)

          再看這個(gè)例子,同樣是 try catch 了異常,結(jié)果卻截然相反

          @Transactional(rollbackFor = Throwable.class)
          public void tryCatchRollBackSuccess(String nameString anotherName
          {
              jdbcTemplate.execute("INSERT INTO USER (NAME) VALUES ('" + name + "')");
              try {
                  // 帶事務(wù),拋異常回滾
                  userService.insertWithTxThrowException(anotherName);
              } catch (RollBackException e) {
                  // do nothing
              }
          }

          @Transactional(rollbackFor = Throwable.class)
          public void insertWithTxThrowException(String namethrows RollBackException 
          {
              jdbcTemplate.execute("INSERT INTO USER (NAME) VALUES ('" + name + "')");
              throw new RollBackException(ROLL_BACK_MESSAGE);
          }

          本例中,兩個(gè)方法的事務(wù)都沒(méi)有設(shè)置propagation屬性,默認(rèn)都是PROPAGATION_REQUIRED。即前者開(kāi)啟事務(wù),后者加入前面開(kāi)啟的事務(wù),二者同屬于一個(gè)物理事務(wù)。insertWithTxThrowException()方法拋出異常,將事務(wù)標(biāo)記為回滾。既然大家是在一條船上,那么后者打翻了船,前者肯定也不能幸免。

          所以tryCatchRollBackSuccess()所執(zhí)行的SQL也必將回滾,執(zhí)行此用例可以查看結(jié)果

          訪問(wèn) http://localhost:8080/h2-console/ ,連接信息如下:

          點(diǎn)擊Connect進(jìn)入控制臺(tái)即可查看表中數(shù)據(jù):

          USER 表確實(shí)沒(méi)有插入數(shù)據(jù),證明了我們的結(jié)論,并且可以看到日志報(bào)錯(cuò):

          Transaction rolled back because it has been marked as rollback-only事務(wù)已經(jīng)回滾,因?yàn)樗粯?biāo)記為必須回滾。

          也就是后面方法觸發(fā)的事務(wù)回滾,讓前面方法的插入也回滾了。

          看到這里,你應(yīng)該能把默認(rèn)的傳播類型PROPAGATION_REQUIRED理解透徹了,本例中是因兩個(gè)方法在同一個(gè)物理事務(wù)下,相互影響從而回滾。

          你可能會(huì)問(wèn),那我如果想讓前后兩個(gè)開(kāi)啟了事務(wù)的方法互不影響該怎么辦呢?

          這就要用到下面要說(shuō)的傳播類型了。Spring Boot 基礎(chǔ)推薦下這個(gè)實(shí)戰(zhàn)教程:

          https://github.com/javastacks/spring-boot-best-practice

          2)、PROPAGATION_REQUIRES_NEW

          字面意思:傳播- 必須-新的

          PROPAGATION_REQUIRES_NEWPROPAGATION_REQUIRED不同的是,其總是開(kāi)啟獨(dú)立的事務(wù),不會(huì)參與到已存在的事務(wù)中,這就保證了兩個(gè)事務(wù)的狀態(tài)相互獨(dú)立,互不影響,不會(huì)因?yàn)橐环降幕貪L而干擾到另一方。

          一圖勝千言,下圖表示他倆物理上不在同一個(gè)事務(wù)內(nèi):

          上圖翻譯成偽代碼是這樣的:

          //Transaction1
          try {
              conn.setAutoCommit(false);
              transactionalMethod1(); 
              conn.commit();
          catch (SQLException e) {
              conn.rollback();
          finally {
              conn.close();
          }
          //Transaction2
          try {
              conn.setAutoCommit(false); 
              transactionalMethod2();
              conn.commit();
          catch (SQLException e) {
              conn.rollback();
          finally {
              conn.close();
          }

          TransactionalMethod1 開(kāi)啟新事務(wù),當(dāng)他調(diào)用同樣需要事務(wù)的TransactionalMethod2時(shí),由于后者的傳播屬性設(shè)置了PROPAGATION_REQUIRES_NEW,所以掛起前面的事務(wù)(至于如何掛起,后面我們會(huì)從源碼中窺見(jiàn)),并開(kāi)啟一個(gè)物理上獨(dú)立于前者的新事務(wù),這樣二者的事務(wù)回滾就不會(huì)相互干擾了。

          點(diǎn)擊關(guān)注公眾號(hào),Java干貨及時(shí)送達(dá)

          還是前面的例子,只需要把insertWithTxThrowException()方法的事務(wù)傳播屬性設(shè)置為Propagation.REQUIRES_NEW就可以互不影響了:

          @Transactional(rollbackFor = Throwable.class)
          public void tryCatchRollBackSuccess(String nameString anotherName
          {
              jdbcTemplate.execute("INSERT INTO USER (NAME) VALUES ('" + name + "')");
              try {
                  // 帶事務(wù),拋異常回滾
                  userService.insertWithTxThrowException(anotherName);
              } catch (RollBackException e) {
                  // do nothing
              }
          }

          @Transactional(rollbackFor = Throwable.classpropagation = Propagation.REQUIRES_NEW)
          public void insertWithTxThrowException(String name) throws RollBackException {
              jdbcTemplate.execute("INSERT INTO USER (NAME) VALUES ('" + name + "')");
              throw new RollBackException(ROLL_BACK_MESSAGE);
          }

          PROPAGATION_REQUIREDPropagation.REQUIRES_NEW已經(jīng)足以應(yīng)對(duì)大部分應(yīng)用場(chǎng)景了,這也是開(kāi)發(fā)中常用的事務(wù)傳播類型。前者要求基于同一個(gè)物理事務(wù),要回滾一起回滾,后者是大家使用獨(dú)立事務(wù)互不干涉。還有一個(gè)場(chǎng)景就是:外部方法和內(nèi)部方法共享一個(gè)事務(wù),但是內(nèi)部事務(wù)的回滾不影響外部事務(wù),外部事務(wù)的回滾可以影響內(nèi)部事務(wù)。這就是嵌套這種傳播類型的使用場(chǎng)景。

          3)、PROPAGATION_NESTED

          字面意思:傳播-嵌套

          PROPAGATION_NESTED可以在一個(gè)已存在的物理事務(wù)上設(shè)置多個(gè)供回滾使用的保存點(diǎn)。這種部分回滾可以讓內(nèi)部事務(wù)在其自己的作用域內(nèi)回滾,與此同時(shí),外部事務(wù)可以在某些操作回滾后繼續(xù)執(zhí)行。其底層實(shí)現(xiàn)就是數(shù)據(jù)庫(kù)的savepoint

          這種傳播機(jī)制比前面兩種都要靈活,看下面的代碼:

          @Transactional(rollbackFor = Throwable.class)
          public void invokeNestedTx(String name,String otherName
          {
              jdbcTemplate.execute("INSERT INTO USER (NAME) VALUES ('" + name + "')");
              try {
                  userService.insertWithTxNested(otherName);
              } catch (RollBackException e) {
                  // do nothing
              }
              // 如果這里拋出異常,將導(dǎo)致兩個(gè)方法都回滾
              // throw new RollBackException(ROLL_BACK_MESSAGE);
          }

          @Transactional(rollbackFor = Throwable.class,propagation = Propagation.NESTED)
          public void insertWithTxNested(String name) throws RollBackException {
              jdbcTemplate.execute("INSERT INTO USER (NAME) VALUES ('" + name + "')");
              throw new RollBackException(ROLL_BACK_MESSAGE);
          }

          外部事務(wù)方法invokeNestedTx()開(kāi)啟事務(wù),內(nèi)部事務(wù)方法insertWithTxNested標(biāo)記為嵌套事務(wù),內(nèi)部事務(wù)的回滾通過(guò)保存點(diǎn)完成,不會(huì)影響外部事務(wù)。而外部方法的回滾,則會(huì)連帶內(nèi)部方法一塊回滾。

          小結(jié):本小節(jié)介紹了 3 種常見(jiàn)的Spring 聲明式事務(wù)傳播屬性,結(jié)合樣例代碼,相信你也對(duì)其有所了解了,接下來(lái)我們從源碼層面看一看,Spring 是如何幫我們簡(jiǎn)化事務(wù)樣板代碼,解放生產(chǎn)力的。

          4、源碼窺探

          在閱讀源碼前,先分析一個(gè)問(wèn)題:我要給一個(gè)方法添加事務(wù),需要做哪些工作呢?

          就算我們自己手寫(xiě),也至少得需要這么四步:

          • 開(kāi)啟事務(wù)
          • 執(zhí)行方法
          • 遇到異常就回滾事務(wù)
          • 正常執(zhí)行后提交事務(wù)

          這不就是典型的AOP嘛~

          沒(méi)錯(cuò),Spring 就是通過(guò) AOP,將我們的事務(wù)方法增強(qiáng),從而完成了事務(wù)的相關(guān)操作。下面給出幾個(gè)關(guān)鍵類及其關(guān)鍵方法的源碼走讀。

          既然是 AOP 那肯定要給事務(wù)寫(xiě)一個(gè)切面來(lái)做這個(gè)事,這個(gè)類就是 TransactionAspectSupport,從命名可以看出,這就是“事務(wù)切面支持類”,他的主要工作就是實(shí)現(xiàn)事務(wù)的執(zhí)行流程,其主要實(shí)現(xiàn)方法為invokeWithinTransaction

          protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
                final InvocationCallback invocation)
           throws Throwable 
          {
              
           // 省略代碼...
              // Standard transaction demarcation with getTransaction and commit/rollback calls.
           // 1、開(kāi)啟事務(wù)
              TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
              try {
            // This is an around advice: Invoke the next interceptor in the chain.
            // This will normally result in a target object being invoked.
            //2、執(zhí)行方法
            retVal = invocation.proceedWithInvocation();
           }
           catch (Throwable ex) {
            // target invocation exception
            // 3、捕獲異常時(shí)的處理
            completeTransactionAfterThrowing(txInfo, ex);
            throw ex;
           }
           finally {
            cleanupTransactionInfo(txInfo);
           }

           if (retVal != null && vavrPresent && VavrDelegate.isVavrTry(retVal)) {
            // Set rollback-only in case of Vavr failure matching our rollback rules...
            TransactionStatus status = txInfo.getTransactionStatus();
            if (status != null && txAttr != null) {
             retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
            }
           }
           //4、執(zhí)行成功,提交事務(wù)
           commitTransactionAfterReturning(txInfo);
           return retVal;
           // 省略代碼...

          結(jié)合課代表增加的這四步注釋,相信你很容易就能看明白。

          搞懂了事務(wù)的主要流程,它的傳播機(jī)制又是怎么實(shí)現(xiàn)的呢?這就要看AbstractPlatformTransactionManager這個(gè)類了,從命名就能看出, 它負(fù)責(zé)事務(wù)管理,其中的handleExistingTransaction方法實(shí)現(xiàn)了事務(wù)傳播邏輯,這里挑PROPAGATION_REQUIRES_NEW的實(shí)現(xiàn)跟一下代碼:

          private TransactionStatus handleExistingTransaction(
             TransactionDefinition definition, Object transaction, boolean debugEnabled)

             throws TransactionException 
          {
            // 省略代碼...
            if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {
             if (debugEnabled) {
              logger.debug("Suspending current transaction, creating new transaction with name [" +
                definition.getName() + "]");
             }
             // 事務(wù)掛起
             SuspendedResourcesHolder suspendedResources = suspend(transaction);
             try {
              return startTransaction(definition, transaction, debugEnabled, suspendedResources);
             }
             catch (RuntimeException | Error beginEx) {
              resumeAfterBeginException(transaction, suspendedResources, beginEx);
              throw beginEx;
             }
            }
               // 省略代碼...
           }

          前文我們知道PROPAGATION_REQUIRES_NEW會(huì)將前一個(gè)事務(wù)掛起,并開(kāi)啟獨(dú)立的新事務(wù),而數(shù)據(jù)庫(kù)是不支持事務(wù)的掛起的,Spring 是如何實(shí)現(xiàn)這一特性的呢?

          通過(guò)源碼可以看到,這里調(diào)用了返回值為SuspendedResourcesHoldersuspend(transaction)方法,它的實(shí)際邏輯由其內(nèi)部的doSuspend(transaction)抽象方法實(shí)現(xiàn)。這里我們使用的是JDBC連接數(shù)據(jù)庫(kù),自然要選擇DataSourceTransactionManager這個(gè)子類去查看其實(shí)現(xiàn),代碼如下:

          protected Object doSuspend(Object transaction) {
            DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
            txObject.setConnectionHolder(null);
            return TransactionSynchronizationManager.unbindResource(obtainDataSource());
           }

          這里是把已有事務(wù)的connection解除,并返回被掛起的資源。在接下來(lái)開(kāi)啟事務(wù)時(shí),會(huì)將該掛起資源一并傳入,這樣當(dāng)內(nèi)層事務(wù)執(zhí)行完成后,可以繼續(xù)執(zhí)行外層被掛起的事務(wù)。

          那么,什么時(shí)候來(lái)繼續(xù)執(zhí)行被掛起的事務(wù)呢?

          事務(wù)的流程,雖然是由TransactionAspectSupport實(shí)現(xiàn)的,但是真正的提交,回滾,是由AbstractPlatformTransactionManager來(lái)完成,在其processCommit(DefaultTransactionStatus status)方法最后的finally塊中,執(zhí)行了cleanupAfterCompletion(status):

          private void cleanupAfterCompletion(DefaultTransactionStatus status) {
            status.setCompleted();
            if (status.isNewSynchronization()) {
             TransactionSynchronizationManager.clear();
            }
            if (status.isNewTransaction()) {
             doCleanupAfterCompletion(status.getTransaction());
            }
             // 有掛起事務(wù)則獲取掛起的資源,繼續(xù)執(zhí)行
            if (status.getSuspendedResources() != null) {
             if (status.isDebug()) {
              logger.debug("Resuming suspended transaction after completion of inner transaction");
             }
             Object transaction = (status.hasTransaction() ? status.getTransaction() : null);
                     
             resume(transaction, (SuspendedResourcesHolder) status.getSuspendedResources());
            }
           }

          這里判斷有掛起的資源將會(huì)恢復(fù)執(zhí)行,至此完成掛起和恢復(fù)事務(wù)的邏輯。

          對(duì)于其他事務(wù)傳播屬性的實(shí)現(xiàn),感興趣的同學(xué)使用課代表的樣例工程,打斷點(diǎn)自己去跟一下源碼。限于篇幅,這里只給出了大概處理流程,源碼里有大量細(xì)節(jié),需要同學(xué)們自己去體驗(yàn),有了上文介紹的主邏輯框架基礎(chǔ),跟蹤源碼查看其他實(shí)現(xiàn)應(yīng)該不怎么費(fèi)勁了。

          5、常見(jiàn)失效場(chǎng)景

          很多人(包括課代表本人)一開(kāi)始使用聲明式事務(wù),都會(huì)覺(jué)得這玩意兒真坑,使用起來(lái)那么多條條框框,一不小心就不生效了。為什么會(huì)有這種感覺(jué)呢?

          爬了多次坑之后,課代表總結(jié)了兩條經(jīng)驗(yàn):

          1. 沒(méi)看官方文檔
          2. 不會(huì)讀源碼

          下面簡(jiǎn)單列舉幾個(gè)失效場(chǎng)景:

          1)非 public 方法不生效

          官網(wǎng)有說(shuō)明:

          Method visibility and @Transactional

          When you use transactional proxies with Spring’s standard configuration, you should apply the @Transactional annotation only to methods with public visibility.

          2)Spring 不支持 redis 集群中的事務(wù)

          redis事務(wù)開(kāi)啟命令是multi,但是 Spring Data Redis 不支持 redis 集群中的 multi 命令,如果使用了聲明式事務(wù),將會(huì)報(bào)錯(cuò):MULTI is currently not supported in cluster mode.

          3)多數(shù)據(jù)源情況下需要為每個(gè)數(shù)據(jù)源配置TransactionManager,并指定transactionManager參數(shù)

          第四部分源碼窺探中已經(jīng)看到實(shí)際執(zhí)行事務(wù)操作的是AbstractPlatformTransactionManager,其為TransactionManager的實(shí)現(xiàn)類,每個(gè)事務(wù)的connection連接都受其管理,如果沒(méi)有配置,無(wú)法完成事務(wù)操作。單數(shù)據(jù)源的情況下正常運(yùn)行,是因?yàn)?SpringBoot 的DataSourceTransactionManagerAutoConfiguration為我們自動(dòng)配置了。

          4)rollbackFor 設(shè)置錯(cuò)誤

          默認(rèn)情況下只回滾非受檢異常(也就是,java.lang.RuntimeException的子類)和java.lang.Error,如果明確知道拋異常就要回滾,建議設(shè)置為@Transactional(rollbackFor = Throwable.class)

          5)AOP不生效問(wèn)題

          其他諸如 MyISAM 不支持,es 不支持等等就不一一列舉了。

          如果感興趣,以上這些在源碼中都能找到解答。

          6、結(jié)束語(yǔ)

          關(guān)于 Spring 的聲明式事務(wù),如果想用好,還真得多 Debug 幾遍源碼,由于 Spring 的源碼細(xì)節(jié)過(guò)于豐富,實(shí)在不適合全部貼到文章里,建議自己去跟一下源碼。熟悉之后就不怕再遇到失效情況了。






          關(guān)注Java技術(shù)棧看更多干貨



          獲取 Spring Boot 實(shí)戰(zhàn)筆記!
          瀏覽 51
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  日本黄色视频。 | 精品亲子伦√区一区三区 | 大香蕉伊人在线手机网 | 丝袜足交视频网站 | 麻W豆W传媒在线看 |