<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>

          @Transactional 的三種失效場景及解決方案

          共 16248字,需瀏覽 33分鐘

           ·

          2021-04-25 10:50

          公眾號關(guān)注 “GitHub今日熱榜
          設(shè)為 “星標”,帶你挖掘更多開發(fā)神器!







          第一種


          Transactional注解標注方法修飾符為非public時,@Transactional注解將會不起作用。例如以下代碼。


          定義一個錯誤的@Transactional標注實現(xiàn),修飾一個默認訪問符的方法


          /**
           * @author zhoujy
           * @date 2018年12月06日
           **/

          @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));
              }

          }


          在同一個包內(nèi),新建調(diào)用對象,進行訪問。


          @Component
          public class InvokcationService {
              @Resource
              private TestServiceImpl testService;
              public void invokeInsertTestWrongModifier(){
                  //調(diào)用@Transactional標注的默認訪問符方法
                  testService.insertTestWrongModifier();
              }
          }


          測試用例


          @RunWith(SpringRunner.class)
          @SpringBootTest
          public class DemoApplicationTests {
             @Resource
             InvokcationService invokcationService;

             @Test
             public void testInvoke(){
                invokcationService.invokeInsertTestWrongModifier();
             }
          }


          以上的訪問方式,導致事務沒開啟,因此在方法拋出異常時,testMapper.insert(new Test(10,20,30));操作不會進行回滾。如果TestServiceImpl#insertTestWrongModifier方法改為public的話將會正常開啟事務,testMapper.insert(new Test(10,20,30));將會進行回滾。


          第二種


          在類內(nèi)部調(diào)用調(diào)用類內(nèi)部@Transactional標注的方法。這種情況下也會導致事務不開啟。示例代碼如下。


          設(shè)置一個內(nèi)部調(diào)用


          /**
           * @author zhoujy
           * @date 2018年12月06日
           **/

          @Component
          public class TestServiceImpl implements TestService {
              @Resource
              TestMapper testMapper;

              @Transactional
              public void insertTestInnerInvoke() {
                  //正常public修飾符的事務方法
                  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)用@Transactional標注的方法。
                  insertTestInnerInvoke();
              }

          }


          測試用例。


          @RunWith(SpringRunner.class)
          @SpringBootTest
          public class DemoApplicationTests {

             @Resource
             TestServiceImpl testService;

             /**
              * 測試內(nèi)部調(diào)用@Transactional標注方法
              */

             @Test
             public void testInnerInvoke(){
                 //測試外部調(diào)用事務方法是否正常
                //testService.insertTestInnerInvoke();
                 //測試內(nèi)部調(diào)用事務方法是否正常
                testService.testInnerInvoke();
             }
          }


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


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


          第三種


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


          /**
           * @author zhoujy
           * @date 2018年12月06日
           **/

          @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) {
                          //運行期間拋異常
                          throw new NeedToInterceptException("need intercept");
                      }
                      testMapper.insert(new Test(210,20,30));
                  }catch (Exception e){
                      System.out.println("i catch exception");
                  }
              }
              
          }


          測試用例代碼如下。


          @RunWith(SpringRunner.class)
          @SpringBootTest
          public class DemoApplicationTests {

             @Resource
             TestServiceImpl testService;

             @Test
             public void testCatchException(){
                testService.insertTestCatchException();
             }
          }


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


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


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


          首先不了解@Transactional注解實現(xiàn)原理的可以看一下另一篇文章,@Transactional注解實現(xiàn)原理,然后下面開始結(jié)合源碼分析下面三種情況。


          第一種


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


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


          spring源碼中,在攔截bean的創(chuàng)建過程,尋找bean適配的切點時,運用到下面的方法,目的就是尋找方法上面的@Transactional信息,如果有,就表示切點BeanFactoryTransactionAttributeSourceAdvisor能夠應用(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的方法對象
             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)) ||
                       //適配查詢方法上的@Transactional注解信息
                       methodMatcher.matches(method, targetClass)) {
                      return true;
                   }
                }
             }

             return false;
          }


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


          • 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)建代理對象


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



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



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


          考慮一種情況,如下面代碼所示。兩個方法都被@Transactional注解標注,但是一個有public修飾符一個沒有,那么這種情況我們可以預見的話,一定會創(chuàng)建代理對象,因為至少有一個public修飾符的@Transactional注解標注方法。


          創(chuàng)建了代理對象,insertTestWrongModifier就會開啟事務嗎?答案是不會。


          /**
           * @author zhoujy
           * @date 2018年12月06日
           **/

          @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));
              }

           


          }


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


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


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


          • AbstractFallbackTransactionAttributeSource#getTransactionAttribute

          • AbstractFallbackTransactionAttributeSource#computeTransactionAttribute


          也就是說還需要找一個方法上的@Transactional注解信息,沒有的話就不執(zhí)行代理@Transactional對應的代理邏輯,直接執(zhí)行方法。沒有了@Transactional注解代理邏輯,就無法開啟事務,這也是上一篇已經(jīng)講到的。


          第二種


          在類內(nèi)部調(diào)用調(diào)用類內(nèi)部@Transactional標注的方法。這種情況下也會導致事務不開啟。


          經(jīng)過對第一種的詳細分析,對這種情況為何不開啟事務管理,原因應該也能猜到;


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


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


          /**
           * @author zhoujy
           * @date 2018年12月06日
           **/

          @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)用事務方法
                  testServiceImpl.insertTestInnerInvoke();
              }

          }


          上面就是使用了代理對象進行事務調(diào)用,所以能夠開啟事務管理,但是實際操作中,沒人會閑的蛋疼這樣子玩~


          第三種


          事務方法內(nèi)部捕捉了異常,沒有拋出新的異常,導致事務操作不會進行回滾。


          這種的話,可能我們比較常見,問題就出在代理邏輯中,我們先看看源碼里賣弄動態(tài)代理邏輯是如何為我們管理事務的,這個過程在我的另一篇文章有提到。


          • 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.
                 //開啟事務
                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è)務方法
                   retVal = invocation.proceedWithInvocation();
                }
                catch (Throwable ex) {
                   // target invocation exception
                    //異常時,在catch邏輯中回滾事務
                   completeTransactionAfterThrowing(txInfo, ex);
                   throw ex;
                }
                finally {
                   cleanupTransactionInfo(txInfo);
                }
                 //提交事務
                commitTransactionAfterReturning(txInfo);
                return retVal;
             }

             else {
               //....................
             }
          }


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



          作者:一擼向北

          出處:blog.csdn.net/qq_20597727/article/details/84900994










          關(guān)注GitHub今日熱榜,專注挖掘好用的開發(fā)工具,致力于分享優(yōu)質(zhì)高效的工具、資源、插件等,助力開發(fā)者成長!







          點個在看 你最好看


          瀏覽 28
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  人人看人人摸人人插 | 久久精品内射国产 | 瑟瑟综合| 午夜精品久久久久久久99黑人 | 国产久久久久久久久久久 |