成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

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

開發 后端
本文介紹了@Transactional 失效的3種場景及解決辦法 ,一起來看看吧。

  [[393149]]

Transactional失效場景

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

  1. /**  
  2.  * @author zhoujy  
  3.  **/  
  4. @Component  
  5. public class TestServiceImpl {  
  6.     @Resource  
  7.     TestMapper testMapper;   
  8.      @Transactional  
  9.     void insertTestWrongModifier() { 
  10.         int re = testMapper.insert(new Test(10,20,30));  
  11.         if (re > 0) {  
  12.             throw new NeedToInterceptException("need intercept");  
  13.         }  
  14.         testMapper.insert(new Test(210,20,30));  
  15.     }  

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

  1. @Component  
  2. public class InvokcationService {  
  3.     @Resource  
  4.     private TestServiceImpl testService;  
  5.     public void invokeInsertTestWrongModifier(){  
  6.         //調用@Transactional標注的默認訪問符方法  
  7.         testService.insertTestWrongModifier(); 
  8.      }  

測試用例: 

  1. @RunWith(SpringRunner.class)  
  2. @SpringBootTest  
  3. public class DemoApplicationTests {  
  4.    @Resource  
  5.    InvokcationService invokcationService;  
  6.    @Test  
  7.    public void  testInvoke(){  
  8.       invokcationService.invokeInsertTestWrongModifier();  
  9.    }  

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

第二種失效場景

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

  1. /**  
  2.  * @author zhoujy  
  3.  **/  
  4. @Component  
  5. public class TestServiceImpl implements TestService { 
  6.     @Resource  
  7.     TestMapper testMapper;  
  8.     @Transactional  
  9.     public void insertTestInnerInvoke() {  
  10.         //正常public修飾符的事務方法  
  11.         int re = testMapper.insert(new Test(10,20,30));  
  12.         if (re > 0) {  
  13.             throw new NeedToInterceptException("need intercept");  
  14.         }  
  15.         testMapper.insert(new Test(210,20,30));  
  16.     }  
  17.     public void testInnerInvoke(){  
  18.         //類內部調用@Transactional標注的方法。  
  19.         insertTestInnerInvoke();  
  20.     }  

測試用例: 

  1. @RunWith(SpringRunner.class)  
  2. @SpringBootTest  
  3. public class DemoApplicationTests {  
  4.    @Resource  
  5.    TestServiceImpl testService;  
  6.    /**  
  7.     * 測試內部調用@Transactional標注方法  
  8.     */  
  9.    @Test  
  10.    public void  testInnerInvoke(){  
  11.        //測試外部調用事務方法是否正常  
  12.       //testService.insertTestInnerInvoke();  
  13.        //測試內部調用事務方法是否正常  
  14.       testService.testInnerInvoke();  
  15.    }  

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

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

第三種失效場景

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

  1. /**  
  2.  * @author zhoujy  
  3.  **/  
  4. @Component  
  5. public class TestServiceImpl implements TestService {  
  6.     @Resource  
  7.     TestMapper testMapper;  
  8.     @Transactional  
  9.     public void insertTestCatchException() {  
  10.         try {  
  11.             int re = testMapper.insert(new Test(10,20,30));  
  12.             if (re > 0) {  
  13.                 //運行期間拋異常  
  14.                 throw new NeedToInterceptException("need intercept");  
  15.             }  
  16.             testMapper.insert(new Test(210,20,30));  
  17.         }catch (Exception e){  
  18.             System.out.println("i catch exception");  
  19.         }  
  20.     } 
  21.  

測試用例代碼如下。 

  1. @RunWith(SpringRunner.class)  
  2. @SpringBootTest  
  3. public class DemoApplicationTests {  
  4.    @Resource  
  5.    TestServiceImpl testService;  
  6.    @Test  
  7.    public void testCatchException(){  
  8.       testService.insertTestCatchException();  
  9.    }  

運行測試用例發現,雖然拋出異常,但是異常被捕捉了,沒有拋出到方法 外, 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) 

  1. public static boolean canApply(Pointcut pc, Class<?> targetClass, boolean hasIntroductions) {  
  2.    Assert.notNull(pc, "Pointcut must not be null");  
  3.    if (!pc.getClassFilter().matches(targetClass)) {  
  4.       return false;  
  5.    }  
  6.    MethodMatcher methodMatcher = pc.getMethodMatcher();  
  7.    if (methodMatcher == MethodMatcher.TRUE) {  
  8.       // No need to iterate the methods if we're matching any method anyway...  
  9.       return true;  
  10.    }  
  11.    IntroductionAwareMethodMatcher introductionAwareMethodMatcher = null 
  12.    if (methodMatcher instanceof IntroductionAwareMethodMatcher) {  
  13.       introductionAwareMethodMatcher = (IntroductionAwareMethodMatcher) methodMatcher; 
  14.    }  
  15.     //遍歷class的方法對象  
  16.    Set<Class<?>> classes = new LinkedHashSet<Class<?>>(ClassUtils.getAllInterfacesForClassAsSet(targetClass));  
  17.    classes.add(targetClass);  
  18.    for (Class<?> clazz : classes) {  
  19.       Method[] methods = ReflectionUtils.getAllDeclaredMethods(clazz);  
  20.       for (Method method : methods) {  
  21.          if ((introductionAwareMethodMatcher != null &&  
  22.                introductionAwareMethodMatcher.matches(method, targetClass, hasIntroductions)) ||  
  23.              //適配查詢方法上的@Transactional注解信息    
  24.              methodMatcher.matches(method, targetClass)) {  
  25.             return true;  
  26.          }  
  27.       }  
  28.    }  
  29.    return false;  

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

AbstractFallbackTransactionAttributeSource#getTransactionAttribute

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

不創建代理對象

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

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

不進行代理調用

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

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

  1. /**  
  2.  * @author zhoujy  
  3.  **/  
  4. @Component  
  5. public class TestServiceImpl implements TestService {  
  6.     @Resource  
  7.     TestMapper testMapper;  
  8.     @Override  
  9.     @Transactional  
  10.     public void insertTest() {  
  11.         int re = testMapper.insert(new Test(10,20,30));  
  12.         if (re > 0) {  
  13.             throw new NeedToInterceptException("need intercept");  
  14.         }  
  15.         testMapper.insert(new Test(210,20,30));  
  16.     }    
  17.     @Transactional  
  18.     void insertTestWrongModifier() {  
  19.         int re = testMapper.insert(new Test(10,20,30));  
  20.         if (re > 0) {  
  21.             throw new NeedToInterceptException("need intercept");  
  22.         }  
  23.         testMapper.insert(new Test(210,20,30));  
  24.     }  

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

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

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

AbstractFallbackTransactionAttributeSource#getTransactionAttribute

  •  AbstractFallbackTransactionAttributeSource#computeTransactionAttribute

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

第二種場景分析

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

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

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

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

  1. /**  
  2.  * @author zhoujy  
  3.  **/  
  4. @Component  
  5. public class TestServiceImpl implements TestService {  
  6.     @Resource 
  7.      TestMapper testMapper;  
  8.     @Resource  
  9.     TestServiceImpl testServiceImpl;  
  10.     @Transactional  
  11.     public void insertTestInnerInvoke() {  
  12.         int re = testMapper.insert(new Test(10,20,30));  
  13.         if (re > 0) {  
  14.             throw new NeedToInterceptException("need intercept");  
  15.         }  
  16.         testMapper.insert(new Test(210,20,30));  
  17.     }  
  18.     public void testInnerInvoke(){  
  19.         //內部調用事務方法 
  20.          testServiceImpl.insertTestInnerInvoke();  
  21.     }  

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

第三種場景分析

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

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

TransactionAspectSupport#invokeWithinTransaction

代碼如下。 

  1. protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final InvocationCallback invocation)  
  2.       throws Throwable {  
  3.    // If the transaction attribute is null, the method is non-transactional.  
  4.    final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);  
  5.    final PlatformTransactionManager tm = determineTransactionManager(txAttr); 
  6.    final String joinpointIdentification = methodIdentification(method, targetClass);  
  7.    if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {  
  8.       // Standard transaction demarcation with getTransaction and commit/rollback calls.  
  9.        //開啟事務  
  10.       TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);  
  11.       Object retVal = null 
  12.       try {  
  13.          // This is an around advice: Invoke the next interceptor in the chain.  
  14.          // This will normally result in a target object being invoked. 
  15.           //反射調用業務方法  
  16.          retVal = invocation.proceedWithInvocation();  
  17.       }  
  18.       catch (Throwable ex) {  
  19.          // target invocation exception  
  20.           //異常時,在catch邏輯中回滾事務  
  21.          completeTransactionAfterThrowing(txInfo, ex);  
  22.          throw ex;  
  23.       }  
  24.       finally {  
  25.          cleanupTransactionInfo(txInfo);  
  26.       }  
  27.        //提交事務 
  28.        commitTransactionAfterReturning(txInfo);  
  29.       return retVal;  
  30.    }  
  31.    else {  
  32.      //....................  
  33.    }  

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

 

責任編輯:龐桂玉 來源: Hollis
相關推薦

2023-09-28 09:07:54

注解失效場景

2023-09-27 16:22:51

SpringMySQL原子性

2023-05-05 07:39:04

Spring事務面試

2024-05-07 08:23:03

Spring@Async配置

2020-04-14 13:32:56

@Transacti失效場景

2025-04-07 11:20:00

KubernetesPodPod容器

2019-08-29 14:29:42

JVM內存 Java

2024-09-09 08:29:25

2023-11-02 07:52:30

Java工具

2009-08-18 16:45:50

Tomcat內存溢出

2012-05-29 16:30:33

Tomcat內存溢出

2024-01-29 08:28:01

Spring事務失效

2021-09-04 07:56:44

Spring事務失效

2022-02-14 16:53:57

Spring項目數據庫

2021-06-26 14:59:13

SpringTransaction執行

2023-07-05 08:45:18

Spring事務失效場景

2022-09-20 22:27:08

事務失效public 修飾

2024-06-18 08:37:25

場景異步編程代碼

2015-03-09 15:41:08

MongoDB查詢超時異常Socket Time

2009-07-27 13:38:10

服務器變慢 Ping
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 国产午夜精品视频 | 国产成人网 | 国产黄色网址在线观看 | 看片91 | 男人的天堂久久 | 亚洲欧美精品久久 | 欧洲一级黄 | 在线看片网站 | 国产免费一区二区三区网站免费 | 在线观看国产 | 亚洲精品视频网站在线观看 | 国产在线观看一区二区 | 日本电影一区二区 | 成人精品一区二区三区中文字幕 | av网站观看 | 青娱乐一区二区 | 亚洲欧美日韩精品 | 91免费看片神器 | 中文字幕第十页 | 精品久久亚洲 | 天天搞天天操 | 一区二区成人 | 男女网站免费观看 | 午夜国产在线 | 一区二区精品视频 | 五月婷婷在线播放 | 久久久久久国产精品 | 久久这里有精品 | 奇米久久| 男人天堂手机在线视频 | 成人三区四区 | 国产在线观看一区二区 | 亚洲专区在线 | 热久久国产| 日韩av一区二区在线观看 | 久久久成人精品 | 美女天天操 | 色婷婷精品国产一区二区三区 | 国产精品一区二区三区久久久 | 日韩在线不卡视频 | 国产精品18hdxxxⅹ在线 |