欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

@Transactional注解不起作用的原因分析及解決

 更新時間:2021年09月28日 11:33:24   作者:一擼向北  
這篇文章主要介紹了@Transactional注解不起作用的原因分析及解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教

Transactional失效場景介紹

第一種

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

定義一個錯誤的@Transactional標(biāo)注實現(xiàn),修飾一個默認(rè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)用對象,進(jìn)行訪問。

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

測試用例

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

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

第二種

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

設(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修飾符的事務(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(){
        //類內(nèi)部調(diào)用@Transactional標(biāo)注的方法。
        insertTestInnerInvoke();
    }
}

測試用例。

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

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

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

第三種

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

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

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

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

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

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

第一種

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

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

spring源碼中,在攔截bean的創(chuàng)建過程,尋找bean適配的切點(diǎn)時,運(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的方法對象
   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ǎn),一步一步調(diào)試跟蹤代碼,最終上面的代碼還會調(diào)用如下方法來判斷。在下面的方法上斷點(diǎn),回頭看看方法調(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的那么將不是代理對象。

在這里插入圖片描述

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

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

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

/**
 * @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)代理對象進(jìn)行代理邏輯調(diào)用時,在cglib創(chuàng)建的代理對象的攔截函數(shù)中CglibAopProxy.DynamicAdvisedInterceptor#intercept,有一個邏輯如下,目的是獲取當(dāng)前被代理對象的當(dāng)前需要執(zhí)行的method適配的aop邏輯。

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

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

AbstractFallbackTransactionAttributeSource#getTransactionAttribute

AbstractFallbackTransactionAttributeSource#computeTransactionAttribute

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

第二種

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

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

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

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

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

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

第三種

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

這種的話,可能我們比較常見,問題就出在代理邏輯中,我們先看看源碼里賣弄動態(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.
       //開啟事務(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
          //異常時,在catch邏輯中回滾事務(wù)
         completeTransactionAfterThrowing(txInfo, ex);
         throw ex;
      }
      finally {
         cleanupTransactionInfo(txInfo);
      }
       //提交事務(wù)
      commitTransactionAfterReturning(txInfo);
      return retVal;
   }
   else {
     //....................
   }
}

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

總結(jié)了以上幾種情況~~~~~~~~~~~~~~~~~~~~~

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • 詳解Java異常處理中throw與throws關(guān)鍵字的用法區(qū)別

    詳解Java異常處理中throw與throws關(guān)鍵字的用法區(qū)別

    這篇文章主要介紹了詳解Java異常處理中throw與throws關(guān)鍵字的用法區(qū)別,這也是Java面試題目中的常客,需要的朋友可以參考下
    2015-11-11
  • 關(guān)于bigDecimal類的精度保留方法

    關(guān)于bigDecimal類的精度保留方法

    這篇文章主要介紹了關(guān)于bigDecimal類的精度保留方法,計算機(jī)存儲的浮點(diǎn)數(shù)受存儲bit位數(shù)影響,只能保證一定范圍內(nèi)精準(zhǔn),超過bit范圍的只能取近似值,Java使用java.math.BigDecimal專門處理小數(shù)精度,需要的朋友可以參考下
    2023-07-07
  • SpringCloud應(yīng)用idea實現(xiàn)可相互調(diào)用的多模塊程序詳解

    SpringCloud應(yīng)用idea實現(xiàn)可相互調(diào)用的多模塊程序詳解

    IDEA 全稱 IntelliJ IDEA,是java編程語言的集成開發(fā)環(huán)境。IntelliJ在業(yè)界被公認(rèn)為最好的Java開發(fā)工具,尤其在智能代碼助手、代碼自動提示、重構(gòu)、JavaEE支持、各類版本工具(git、svn等)、JUnit、CVS整合、代碼分析、 創(chuàng)新的GUI設(shè)計等方面的功能可以說是超常的
    2022-07-07
  • java實現(xiàn)自定義表格渲染和編輯

    java實現(xiàn)自定義表格渲染和編輯

    這篇文章主要為大家詳細(xì)介紹了java如何實現(xiàn)自定義表格渲染和編輯,文中的示例代碼講解詳細(xì),具有一定的借鑒價值,感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2024-04-04
  • SpringBoot整合H2數(shù)據(jù)庫的操作方法

    SpringBoot整合H2數(shù)據(jù)庫的操作方法

    H2是一個Java語言編寫的嵌入式數(shù)據(jù)庫,它不受平臺的限制,同時H2提供了一個十分方便的web控制臺,用于操作和管理數(shù)據(jù)庫內(nèi)容,本文介紹SpringBoot整合H2數(shù)據(jù)庫的方法,感興趣的朋友一起看看吧
    2024-01-01
  • java異步編程詳解

    java異步編程詳解

    這篇文章主要介紹了java異步編程,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-04-04
  • swagger添加權(quán)限驗證保證API(接口)安全性(兩種方法)

    swagger添加權(quán)限驗證保證API(接口)安全性(兩種方法)

    這篇文章主要介紹了swagger添加權(quán)限驗證保證API(接口)安全性(兩種方法),本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-01-01
  • 通過Java實現(xiàn)RSA加密與驗證的方法詳解

    通過Java實現(xiàn)RSA加密與驗證的方法詳解

    RSA是一種非對稱加密算法,是目前廣泛應(yīng)用于加密和數(shù)字簽名領(lǐng)域的一種加密算法,本文主要講述如何通過Java實現(xiàn)RSA加密與驗證,應(yīng)用場景為與其他平臺對接接口時,通過RSA加密和解密驗證請求的有效性,在對接時雙方互換公鑰,需要的朋友可以參考下
    2023-12-12
  • Java生成非對稱型加密公鑰和私鑰的方法

    Java生成非對稱型加密公鑰和私鑰的方法

    這篇文章主要介紹了Java生成非對稱型加密公鑰和私鑰的方法,涉及java非對稱加密的原理與實現(xiàn)技巧,具有一定參考借鑒價值,需要的朋友可以參考下
    2015-07-07
  • MyBatis通用的10種寫法總結(jié)大全

    MyBatis通用的10種寫法總結(jié)大全

    這篇文章主要給大家介紹了關(guān)于MyBatis通用的10種寫法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2018-11-11

最新評論