91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

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

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

事務注解@Transactional失效的場景及解決辦法

發布時間:2021-08-30 22:21:57 來源:億速云 閱讀:165 作者:chen 欄目:編程語言

本篇內容主要講解“事務注解@Transactional失效的場景及解決辦法”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“事務注解@Transactional失效的場景及解決辦法”吧!

Transactional失效場景

第一種 Transactional注解標注方法修飾符為非public時,@Transactional注解將會不起作用。例如以下代碼,定義一個錯誤的@Transactional標注實現,修飾一個默認訪問符的方法:

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

在同一個包內,新建調用對象,進行訪問。

@Component  public class InvokcationService {      @Resource      private TestServiceImpl testService;      public void invokeInsertTestWrongModifier(){          //調用@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));將會進行回滾。

第二種失效場景

在類內部調用調用類內部@Transactional標注的方法,這種情況下也會導致事務不開啟。示例代碼如下,設置一個內部調用:

/**   * @author zhoujy   **/  @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(){          //類內部調用@Transactional標注的方法。          insertTestInnerInvoke();      }  }

測試用例:

@RunWith(SpringRunner.class)  @SpringBootTest  public class DemoApplicationTests {     @Resource     TestServiceImpl testService;     /**      * 測試內部調用@Transactional標注方法      */     @Test     public void  testInnerInvoke(){         //測試外部調用事務方法是否正常        //testService.insertTestInnerInvoke();         //測試內部調用事務方法是否正常        testService.testInnerInvoke();     }  }

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

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

第三種失效場景

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

/**   * @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) {                  //運行期間拋異常                  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();     }  }

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

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

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

第一種場景分析

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

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

spring源碼中,在攔截bean的創建過程,尋找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;  }

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

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;     }     //后面省略.......   }

不創建代理對象

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

事務注解@Transactional失效的場景及解決辦法

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

事務注解@Transactional失效的場景及解決辦法

不進行代理調用

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

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

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

原因是在動態代理對象進行代理邏輯調用時,在cglib創建的代理對象的攔截函數中CglibAopProxy.DynamicAdvisedInterceptor#intercept,有一個邏輯如下,目的是獲取當前被代理對象的當前需要執行的method適配的aop邏輯。

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

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

AbstractFallbackTransactionAttributeSource#getTransactionAttribute

  •  AbstractFallbackTransactionAttributeSource#computeTransactionAttribute

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

第二種場景分析

在類內部調用調用類內部@Transactional標注的方法。這種情況下也會導致事務不開啟。

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

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

其實我們可以這樣玩,內部調用也能實現開啟事務,代碼如下。

/**   * @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(){          //內部調用事務方法          testServiceImpl.insertTestInnerInvoke();      }  }

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

第三種場景分析

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

這種的話,可能我們比較常見,問題就出在代理邏輯中,我們先看看源碼里賣弄動態代理邏輯是如何為我們管理事務的。

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

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

到此,相信大家對“事務注解@Transactional失效的場景及解決辦法”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

珲春市| 新乡县| 大同市| 阳曲县| 临高县| 临漳县| 南雄市| 普安县| 嵩明县| 通渭县| 申扎县| 贵州省| 涡阳县| 咸宁市| 景谷| 尉氏县| 永宁县| 烟台市| 从江县| 成安县| 南汇区| 营山县| 甘德县| 海盐县| 嘉荫县| 淮北市| 泗阳县| 古田县| 广宗县| 连南| 玉溪市| 壤塘县| 滕州市| 明星| 南华县| 崇仁县| 根河市| 阳西县| 五台县| 翁源县| 屯门区|