溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

事務(wù)注解@Transactional失效的場(chǎng)景及解決辦法

發(fā)布時(shí)間:2021-08-30 22:21:57 來(lái)源:億速云 閱讀:165 作者:chen 欄目:編程語(yǔ)言

本篇內(nèi)容主要講解“事務(wù)注解@Transactional失效的場(chǎng)景及解決辦法”,感興趣的朋友不妨來(lái)看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來(lái)帶大家學(xué)習(xí)“事務(wù)注解@Transactional失效的場(chǎng)景及解決辦法”吧!

Transactional失效場(chǎng)景

第一種 Transactional注解標(biāo)注方法修飾符為非public時(shí),@Transactional注解將會(huì)不起作用。例如以下代碼,定義一個(gè)錯(cuò)誤的@Transactional標(biāo)注實(shí)現(xiàn),修飾一個(gè)默認(rèn)訪(fǎng)問(wèn)符的方法:

/**   * @author zhoujy   **/  @Component  public class TestServiceImpl {      @Resource      TestMapper testMapper;        @Transactional      void insertTestWrongModifier() {         int re = testMapper.insert(new Test(10,20,30));          if (re > 0) {              throw new NeedToInterceptException("need intercept");          }          testMapper.insert(new Test(210,20,30));      }  }

在同一個(gè)包內(nèi),新建調(diào)用對(duì)象,進(jìn)行訪(fǎng)問(wèn)。

@Component  public class InvokcationService {      @Resource      private TestServiceImpl testService;      public void invokeInsertTestWrongModifier(){          //調(diào)用@Transactional標(biāo)注的默認(rèn)訪(fǎng)問(wèn)符方法          testService.insertTestWrongModifier();      }  }

測(cè)試用例:

@RunWith(SpringRunner.class)  @SpringBootTest  public class DemoApplicationTests {     @Resource     InvokcationService invokcationService;     @Test     public void  testInvoke(){        invokcationService.invokeInsertTestWrongModifier();     }  }

以上的訪(fǎng)問(wèn)方式,導(dǎo)致事務(wù)沒(méi)開(kāi)啟,因此在方法拋出異常時(shí),testMapper.insert(new Test(10,20,30));操作不會(huì)進(jìn)行回滾。如果TestServiceImpl#insertTestWrongModifier方法改為public的話(huà)將會(huì)正常開(kāi)啟事務(wù),testMapper.insert(new Test(10,20,30));將會(huì)進(jìn)行回滾。

第二種失效場(chǎng)景

在類(lèi)內(nèi)部調(diào)用調(diào)用類(lèi)內(nèi)部@Transactional標(biāo)注的方法,這種情況下也會(huì)導(dǎo)致事務(wù)不開(kāi)啟。示例代碼如下,設(shè)置一個(gè)內(nèi)部調(diào)用:

/**   * @author zhoujy   **/  @Component  public class TestServiceImpl implements TestService {     @Resource      TestMapper testMapper;      @Transactional      public void insertTestInnerInvoke() {          //正常public修飾符的事務(wù)方法          int re = testMapper.insert(new Test(10,20,30));          if (re > 0) {              throw new NeedToInterceptException("need intercept");          }          testMapper.insert(new Test(210,20,30));      }      public void testInnerInvoke(){          //類(lèi)內(nèi)部調(diào)用@Transactional標(biāo)注的方法。          insertTestInnerInvoke();      }  }

測(cè)試用例:

@RunWith(SpringRunner.class)  @SpringBootTest  public class DemoApplicationTests {     @Resource     TestServiceImpl testService;     /**      * 測(cè)試內(nèi)部調(diào)用@Transactional標(biāo)注方法      */     @Test     public void  testInnerInvoke(){         //測(cè)試外部調(diào)用事務(wù)方法是否正常        //testService.insertTestInnerInvoke();         //測(cè)試內(nèi)部調(diào)用事務(wù)方法是否正常        testService.testInnerInvoke();     }  }

上面就是使用的測(cè)試代碼,運(yùn)行測(cè)試知道,外部調(diào)用事務(wù)方法能夠征程開(kāi)啟事務(wù),testMapper.insert(new Test(10,20,30))操作將會(huì)被回滾;

然后運(yùn)行另外一個(gè)測(cè)試用例,調(diào)用一個(gè)方法在類(lèi)內(nèi)部調(diào)用內(nèi)部被@Transactional標(biāo)注的事務(wù)方法,運(yùn)行結(jié)果是事務(wù)不會(huì)正常開(kāi)啟,testMapper.insert(new Test(10,20,30))操作將會(huì)保存到數(shù)據(jù)庫(kù)不會(huì)進(jìn)行回滾。

第三種失效場(chǎng)景

事務(wù)方法內(nèi)部捕捉了異常,沒(méi)有拋出新的異常,導(dǎo)致事務(wù)操作不會(huì)進(jìn)行回滾。示例代碼如下。

/**   * @author zhoujy   **/  @Component  public class TestServiceImpl implements TestService {      @Resource      TestMapper testMapper;      @Transactional      public void insertTestCatchException() {          try {              int re = testMapper.insert(new Test(10,20,30));              if (re > 0) {                  //運(yùn)行期間拋異常                  throw new NeedToInterceptException("need intercept");              }              testMapper.insert(new Test(210,20,30));          }catch (Exception e){              System.out.println("i catch exception");          }      }  }

測(cè)試用例代碼如下。

@RunWith(SpringRunner.class)  @SpringBootTest  public class DemoApplicationTests {     @Resource     TestServiceImpl testService;     @Test     public void testCatchException(){        testService.insertTestCatchException();     }  }

運(yùn)行測(cè)試用例發(fā)現(xiàn),雖然拋出異常,但是異常被捕捉了,沒(méi)有拋出到方法 外, testMapper.insert(new Test(210,20,30))操作并沒(méi)有回滾。

以上三種就是@Transactional注解不起作用,@Transactional注解失效的主要原因。下面結(jié)合spring中對(duì)于@Transactional的注解實(shí)現(xiàn)源碼分析為何導(dǎo)致@Transactional注解不起作用。

@Transactional注解不起作用原理分析

第一種場(chǎng)景分析

@Transactional注解標(biāo)注方法修飾符為非public時(shí),@Transactional注解將會(huì)不起作用。這里分析 的原因是,@Transactional是基于動(dòng)態(tài)代理實(shí)現(xiàn)的,@Transactional注解實(shí)現(xiàn)原理中分析了實(shí)現(xiàn)方法,在bean初始化過(guò)程中,對(duì)含有@Transactional標(biāo)注的bean實(shí)例創(chuàng)建代理對(duì)象,這里就存在一個(gè)spring掃描@Transactional注解信息的過(guò)程,不幸的是源碼中體現(xiàn),標(biāo)注@Transactional的方法如果修飾符不是public,那么就默認(rèn)方法的@Transactional信息為空,那么將不會(huì)對(duì)bean進(jìn)行代理對(duì)象創(chuàng)建或者不會(huì)對(duì)方法進(jìn)行代理調(diào)用

@Transactional注解實(shí)現(xiàn)原理中,介紹了如何判定一個(gè)bean是否創(chuàng)建代理對(duì)象,大概邏輯是。根據(jù)spring創(chuàng)建好一個(gè)aop切點(diǎn)BeanFactoryTransactionAttributeSourceAdvisor實(shí)例,遍歷當(dāng)前bean的class的方法對(duì)象,判斷方法上面的注解信息是否包含@Transactional,如果bean任何一個(gè)方法包含@Transactional注解信息,那么就是適配這個(gè)BeanFactoryTransactionAttributeSourceAdvisor切點(diǎn)。則需要?jiǎng)?chuàng)建代理對(duì)象,然后代理邏輯為我們管理事務(wù)開(kāi)閉邏輯。

spring源碼中,在攔截bean的創(chuàng)建過(guò)程,尋找bean適配的切點(diǎn)時(shí),運(yùn)用到下面的方法,目的就是尋找方法上面的@Transactional信息,如果有,就表示切點(diǎn)BeanFactoryTransactionAttributeSourceAdvisor能夠應(yīng)用(canApply)到bean中,

AopUtils#canApply(org.springframework.aop.Pointcut, java.lang.Class<?>, boolean)

public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {     Assert.notNull(pc, "Pointcut must not be null");     if (!pc.getClassFilter().matches(targetClass)) {        return false;     }     MethodMatcher methodMatcher = pc.getMethodMatcher();     if (methodMatcher == MethodMatcher.TRUE) {        // No need to iterate the methods if we're matching any method anyway...        return true;     }     IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null;     if (methodMatcher instanceof IntroductionAwareMethodMatcher) {        introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher;    }      //遍歷class的方法對(duì)象     Set<Class<?>> classes = new LinkedHashSet<Class<?>>(ClassUtils.getAllInterfacesForClassAsSet(targetClass));     classes.add(targetClass);     for (Class<?> clazz : classes) {        Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);        for (Method method : methods) {           if ((introductionAwareMethodMatcher != null &&                 introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions)) ||               //適配查詢(xún)方法上的@Transactional注解信息                 methodMatcher.matches(method, targetClass)) {              return true;           }        }     }     return false;  }

我們可以在上面的方法打斷點(diǎn),一步一步調(diào)試跟蹤代碼,最終上面的代碼還會(huì)調(diào)用如下方法來(lái)判斷。在下面的方法上斷點(diǎn),回頭看看方法調(diào)用堆棧也是不錯(cuò)的方式跟蹤。

AbstractFallbackTransactionAttributeSource#getTransactionAttribute

  •  AbstractFallbackTransactionAttributeSource#computeTransactionAttribute 

protected TransactionAttribute computeTransactionAttribute(Method method, Class<?> targetClass) {     // Don't allow no-public methods as required.     //非public 方法,返回@Transactional信息一律是null     if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {        return null;     }     //后面省略.......   }

不創(chuàng)建代理對(duì)象

所以,如果所有方法上的修飾符都是非public的時(shí)候,那么將不會(huì)創(chuàng)建代理對(duì)象。以一開(kāi)始的測(cè)試代碼為例,如果正常的修飾符的testService是下面圖片中的,經(jīng)過(guò)cglib創(chuàng)建的代理對(duì)象。

事務(wù)注解@Transactional失效的場(chǎng)景及解決辦法

如果class中的方法都是非public的那么將不是代理對(duì)象。

事務(wù)注解@Transactional失效的場(chǎng)景及解決辦法

不進(jìn)行代理調(diào)用

考慮一種情況,如下面代碼所示。兩個(gè)方法都被@Transactional注解標(biāo)注,但是一個(gè)有public修飾符一個(gè)沒(méi)有,那么這種情況我們可以預(yù)見(jiàn)的話(huà),一定會(huì)創(chuàng)建代理對(duì)象,因?yàn)橹辽儆幸粋€(gè)public修飾符的@Transactional注解標(biāo)注方法。

創(chuàng)建了代理對(duì)象,insertTestWrongModifier就會(huì)開(kāi)啟事務(wù)嗎?答案是不會(huì)。

/**   * @author zhoujy   **/  @Component  public class TestServiceImpl implements TestService {      @Resource      TestMapper testMapper;      @Override      @Transactional      public void insertTest() {          int re = testMapper.insert(new Test(10,20,30));          if (re > 0) {              throw new NeedToInterceptException("need intercept");          }          testMapper.insert(new Test(210,20,30));      }        @Transactional      void insertTestWrongModifier() {          int re = testMapper.insert(new Test(10,20,30));          if (re > 0) {              throw new NeedToInterceptException("need intercept");          }          testMapper.insert(new Test(210,20,30));      }  }

原因是在動(dòng)態(tài)代理對(duì)象進(jìn)行代理邏輯調(diào)用時(shí),在cglib創(chuàng)建的代理對(duì)象的攔截函數(shù)中CglibAopProxy.DynamicAdvisedInterceptor#intercept,有一個(gè)邏輯如下,目的是獲取當(dāng)前被代理對(duì)象的當(dāng)前需要執(zhí)行的method適配的aop邏輯。

List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass);

而針對(duì)@Transactional注解查找aop邏輯過(guò)程,相似地,也是執(zhí)行一次

AbstractFallbackTransactionAttributeSource#getTransactionAttribute

  •  AbstractFallbackTransactionAttributeSource#computeTransactionAttribute

也就是說(shuō)還需要找一個(gè)方法上的@Transactional注解信息,沒(méi)有的話(huà)就不執(zhí)行代理@Transactional對(duì)應(yīng)的代理邏輯,直接執(zhí)行方法。沒(méi)有了@Transactional注解代理邏輯,就無(wú)法開(kāi)啟事務(wù),這也是上一篇已經(jīng)講到的。

第二種場(chǎng)景分析

在類(lèi)內(nèi)部調(diào)用調(diào)用類(lèi)內(nèi)部@Transactional標(biāo)注的方法。這種情況下也會(huì)導(dǎo)致事務(wù)不開(kāi)啟。

經(jīng)過(guò)對(duì)第一種的詳細(xì)分析,對(duì)這種情況為何不開(kāi)啟事務(wù)管理,原因應(yīng)該也能猜到;

既然事務(wù)管理是基于動(dòng)態(tài)代理對(duì)象的代理邏輯實(shí)現(xiàn)的,那么如果在類(lèi)內(nèi)部調(diào)用類(lèi)內(nèi)部的事務(wù)方法,這個(gè)調(diào)用事務(wù)方法的過(guò)程并不是通過(guò)代理對(duì)象來(lái)調(diào)用的,而是直接通過(guò)this對(duì)象來(lái)調(diào)用方法,繞過(guò)的代理對(duì)象,肯定就是沒(méi)有代理邏輯了。

其實(shí)我們可以這樣玩,內(nèi)部調(diào)用也能實(shí)現(xiàn)開(kāi)啟事務(wù),代碼如下。

/**   * @author zhoujy   **/  @Component  public class TestServiceImpl implements TestService {      @Resource      TestMapper testMapper;      @Resource      TestServiceImpl testServiceImpl;      @Transactional      public void insertTestInnerInvoke() {          int re = testMapper.insert(new Test(10,20,30));          if (re > 0) {              throw new NeedToInterceptException("need intercept");          }          testMapper.insert(new Test(210,20,30));      }      public void testInnerInvoke(){          //內(nèi)部調(diào)用事務(wù)方法          testServiceImpl.insertTestInnerInvoke();      }  }

上面就是使用了代理對(duì)象進(jìn)行事務(wù)調(diào)用,所以能夠開(kāi)啟事務(wù)管理,但是實(shí)際操作中,沒(méi)人會(huì)閑的蛋疼這樣子玩~

第三種場(chǎng)景分析

事務(wù)方法內(nèi)部捕捉了異常,沒(méi)有拋出新的異常,導(dǎo)致事務(wù)操作不會(huì)進(jìn)行回滾。

這種的話(huà),可能我們比較常見(jiàn),問(wèn)題就出在代理邏輯中,我們先看看源碼里賣(mài)弄?jiǎng)討B(tài)代理邏輯是如何為我們管理事務(wù)的。

TransactionAspectSupport#invokeWithinTransaction

代碼如下。

protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final InvocationCallback invocation)        throws Throwable {     // If the transaction attribute is null, the method is non-transactional.     final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);     final PlatformTransactionManager tm = determineTransactionManager(txAttr);    final String joinpointIdentification = methodIdentification(method, targetClass);     if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {        // Standard transaction demarcation with getTransaction and commit/rollback calls.         //開(kāi)啟事務(wù)        TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);        Object retVal = null;        try {           // This is an around advice: Invoke the next interceptor in the chain.           // This will normally result in a target object being invoked.           //反射調(diào)用業(yè)務(wù)方法           retVal = invocation.proceedWithInvocation();        }        catch (Throwable ex) {           // target invocation exception            //異常時(shí),在catch邏輯中回滾事務(wù)           completeTransactionAfterThrowing(txInfo, ex);           throw ex;        }        finally {           cleanupTransactionInfo(txInfo);        }         //提交事務(wù)        commitTransactionAfterReturning(txInfo);        return retVal;     }     else {       //....................     }  }

所以看了上面的代碼就一目了然了,事務(wù)想要回滾,必須能夠在這里捕捉到異常才行,如果異常中途被捕捉掉,那么事務(wù)將不會(huì)回滾。

到此,相信大家對(duì)“事務(wù)注解@Transactional失效的場(chǎng)景及解決辦法”有了更深的了解,不妨來(lái)實(shí)際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢(xún),關(guān)注我們,繼續(xù)學(xué)習(xí)!

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI