<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 注解失效的3種原因及解決辦法

          共 10670字,需瀏覽 22分鐘

           ·

          2022-04-20 20:27

          往期熱門文章:

          1、初看一臉懵逼,看懂直接跪下!
          2、再見 BeanUtils!性能真拉跨!
          3、被公司辭退拿了22萬補償金,原東家稱回來后每月漲薪7000,只要退還22萬
          4、用 Dubbo 傳輸文件?被老板一頓揍!
          5、45 個 Git 經(jīng)典操作場景,專治不會合代碼!

          Transactional失效場景介紹

          第一種 Transactional注解標注方法修飾符為非public時,@Transactional注解將會不起作用。例如以下代碼。
          定義一個錯誤的@Transactional標注實現(xià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));??
          ????}??
          ??
          }??
          在同一個包內,新建調用對象,進行訪問。
          @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))操作將會保存到數(shù)據(jù)庫不會進行回滾。

          第三種

          事務方法內部捕捉了異常,沒有拋出新的異常,導致事務操作不會進行回滾。示例代碼如下。
          /**??
          ?*?@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();??
          ???}??
          }??
          運行測試用例發(fā)現(xiàn),雖然拋出異常,但是異常被捕捉了,沒有拋出到方法 外, testMapper.insert(new Test(210,20,30))操作并沒有回滾。
          以上三種就是@Transactional注解不起作用,@Transactional注解失效的主要原因。下面結合spring中對于@Transactional的注解實現(xiàn)源碼分析為何導致@Transactional注解不起作用。

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

          第一種

          @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)建或者不會對方法進行代理調用
          @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>?classes?=?new?LinkedHashSet>(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;??
          ???}??
          ???//后面省略.......??
          ?}??

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

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

          不進行代理調用

          考慮一種情況,如下面代碼所示。兩個方法都被@Transactional注解標注,但是一個有public修飾符一個沒有,那么這種情況我們可以預見的話,一定會創(chuàng)建代理對象,因為至少有一個public修飾符的@Transactional注解標注方法。
          創(chuàng)建了代理對象,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));??
          ????}??
          }??
          原因是在動態(tài)代理對象進行代理邏輯調用時,在cglib創(chuàng)建的代理對象的攔截函數(shù)中CglibAopProxy.DynamicAdvisedInterceptor#intercept,有一個邏輯如下,目的是獲取當前被代理對象的當前需要執(zhí)行的method適配的aop邏輯。
          List?chain?=?this.advised.getInterceptorsAndDynamicInterceptionAdvice(method,?targetClass);??
          而針對@Transactional注解查找aop邏輯過程,相似地,也是執(zhí)行一次
          AbstractFallbackTransactionAttributeSource#getTransactionAttribute
          • AbstractFallbackTransactionAttributeSource#computeTransactionAttribute
          也就是說還需要找一個方法上的@Transactional注解信息,沒有的話就不執(zhí)行代理@Transactional對應的代理邏輯,直接執(zhí)行方法。沒有了@Transactional注解代理邏輯,就無法開啟事務,這也是上一篇已經(jīng)講到的。

          第二種

          在類內部調用調用類內部@Transactional標注的方法。這種情況下也會導致事務不開啟。
          經(jīng)過對第一種的詳細分析,對這種情況為何不開啟事務管理,原因應該也能猜到;
          既然事務管理是基于動態(tài)代理對象的代理邏輯實現(xiàn)的,那么如果在類內部調用類內部的事務方法,這個調用事務方法的過程并不是通過代理對象來調用的,而是直接通過this對象來調用方法,繞過的代理對象,肯定就是沒有代理邏輯了。
          其實我們可以這樣玩,內部調用也能實現(xiàn)開啟事務,代碼如下。
          /**??
          ?*?@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();??
          ????}??
          ??
          }??
          上面就是使用了代理對象進行事務調用,所以能夠開啟事務管理,但是實際操作中,沒人會閑的蛋疼這樣子玩~

          第三種

          事務方法內部捕捉了異常,沒有拋出新的異常,導致事務操作不會進行回滾。
          這種的話,可能我們比較常見,問題就出在代理邏輯中,我們先看看源碼里賣弄動態(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.??
          ??????????//反射調用業(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

          往期熱門文章:

          1、我滴個乖乖,我復現(xiàn)了Spring的漏洞,害怕!
          2、分布式鎖用 Redis 還是 Zookeeper?
          3、最適合晚上睡不著看的 8 個網(wǎng)站,建議收藏哦
          4、String長度有限制嗎?
          5、14家互聯(lián)網(wǎng)公司裁員(1-2月裁員清單)
          6、Redis實現(xiàn)分布式鎖的8大坑!切記!
          7、請立即卸載這款 IDEA 插件!
          8、Thread.sleep(0) 到底有什么用?
          9、為什么不建議用try catch處理異常?
          10、MySQL 為啥不能用 UUID 做主鍵?

          瀏覽 19
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

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

          手機掃一掃分享

          分享
          舉報
          <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青草国产精品视频无码一区 | 91四虎影院在线 |