<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種原因及解決辦法

          共 10375字,需瀏覽 21分鐘

           ·

          2022-04-27 21:05

          Transactional失效場(chǎng)景介紹

          第一種 Transactional注解標(biāo)注方法修飾符為非public時(shí),@Transactional注解將會(huì)不起作用。例如以下代碼。
          定義一個(gè)錯(cuò)誤的@Transactional標(biāo)注實(shí)現(xiàn),修飾一個(gè)默認(rèn)訪問(wè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));??
          ????}??
          ??
          }??
          在同一個(gè)包內(nèi),新建調(diào)用對(duì)象,進(jìn)行訪問(wèn)。
          @Component??
          public?class?InvokcationService?{??
          ????@Resource??
          ????private?TestServiceImpl?testService;??
          ????public?void?invokeInsertTestWrongModifier(){??
          ????????//調(diào)用@Transactional標(biāo)注的默認(rèn)訪問(wèn)符方法??
          ????????testService.insertTestWrongModifier();??
          ????}??
          }??
          測(cè)試用例
          @RunWith(SpringRunner.class)??
          @SpringBootTest??
          public?class?DemoApplicationTests?
          {??
          ???@Resource??
          ???InvokcationService?invokcationService;??
          ??
          ???@Test??
          ???public?void??testInvoke(){??
          ??????invokcationService.invokeInsertTestWrongModifier();??
          ???}??
          }??
          以上的訪問(wèn)方式,導(dǎo)致事務(wù)沒(méi)開(kāi)啟,因此在方法拋出異常時(shí),testMapper.insert(new Test(10,20,30));操作不會(huì)進(jìn)行回滾。如果TestServiceImpl#insertTestWrongModifier方法改為public的話將會(huì)正常開(kāi)啟事務(wù),testMapper.insert(new Test(10,20,30));將會(huì)進(jìn)行回滾。

          第二種

          在類內(nèi)部調(diào)用調(diào)用類內(nèi)部@Transactional標(biāo)注的方法。這種情況下也會(huì)導(dǎo)致事務(wù)不開(kāi)啟。示例代碼如下。
          設(shè)置一個(gè)內(nèi)部調(diào)用
          /**??
          ?*?@author?zhoujy??
          ?**/
          ??
          @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();??
          ????}??
          ??
          }??
          測(cè)試用例。
          @RunWith(SpringRunner.class)??
          @SpringBootTest??
          public?class?DemoApplicationTests?
          {??
          ??
          ???@Resource??
          ???TestServiceImpl?testService;??
          ??
          ???/**??
          ????*?測(cè)試內(nèi)部調(diào)用@Transactional標(biāo)注方法??
          ????*/
          ??
          ???@Test??
          ???public?void??testInnerInvoke(){??
          ???????//測(cè)試外部調(diào)用事務(wù)方法是否正常??
          ??????//testService.insertTestInnerInvoke();??
          ???????//測(cè)試內(nèi)部調(diào)用事務(wù)方法是否正常??
          ??????testService.testInnerInvoke();??
          ???}??
          }??
          上面就是使用的測(cè)試代碼,運(yùn)行測(cè)試知道,外部調(diào)用事務(wù)方法能夠征程開(kāi)啟事務(wù),testMapper.insert(new Test(10,20,30))操作將會(huì)被回滾;
          然后運(yùn)行另外一個(gè)測(cè)試用例,調(diào)用一個(gè)方法在類內(nèi)部調(diào)用內(nèi)部被@Transactional標(biāo)注的事務(wù)方法,運(yùn)行結(jié)果是事務(wù)不會(huì)正常開(kāi)啟,testMapper.insert(new Test(10,20,30))操作將會(huì)保存到數(shù)據(jù)庫(kù)不會(huì)進(jìn)行回滾。

          第三種

          事務(wù)方法內(nèi)部捕捉了異常,沒(méi)有拋出新的異常,導(dǎo)致事務(wù)操作不會(huì)進(jìn)行回滾。示例代碼如下。
          /**??
          ?*?@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)?{??
          ????????????????//運(yùn)行期間拋異常??
          ????????????????throw?new?NeedToInterceptException("need?intercept");??
          ????????????}??
          ????????????testMapper.insert(new?Test(210,20,30));??
          ????????}catch?(Exception?e){??
          ????????????System.out.println("i?catch?exception");??
          ????????}??
          ????}??
          ??????
          }??
          測(cè)試用例代碼如下。
          @RunWith(SpringRunner.class)??
          @SpringBootTest??
          public?class?DemoApplicationTests?
          {??
          ??
          ???@Resource??
          ???TestServiceImpl?testService;??
          ??
          ???@Test??
          ???public?void?testCatchException(){??
          ??????testService.insertTestCatchException();??
          ???}??
          }??
          運(yùn)行測(cè)試用例發(fā)現(xiàn),雖然拋出異常,但是異常被捕捉了,沒(méi)有拋出到方法 外,?testMapper.insert(new Test(210,20,30))操作并沒(méi)有回滾。
          以上三種就是@Transactional注解不起作用,@Transactional注解失效的主要原因。下面結(jié)合spring中對(duì)于@Transactional的注解實(shí)現(xiàn)源碼分析為何導(dǎo)致@Transactional注解不起作用。

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

          第一種

          @Transactional注解標(biāo)注方法修飾符為非public時(shí),@Transactional注解將會(huì)不起作用。這里分析 的原因是,@Transactional是基于動(dòng)態(tài)代理實(shí)現(xiàn)的,@Transactional注解實(shí)現(xiàn)原理中分析了實(shí)現(xiàn)方法,在bean初始化過(guò)程中,對(duì)含有@Transactional標(biāo)注的bean實(shí)例創(chuàng)建代理對(duì)象,這里就存在一個(gè)spring掃描@Transactional注解信息的過(guò)程,不幸的是源碼中體現(xiàn),標(biāo)注@Transactional的方法如果修飾符不是public,那么就默認(rèn)方法的@Transactional信息為空,那么將不會(huì)對(duì)bean進(jìn)行代理對(duì)象創(chuàng)建或者不會(huì)對(duì)方法進(jìn)行代理調(diào)用
          @Transactional注解實(shí)現(xiàn)原理中,介紹了如何判定一個(gè)bean是否創(chuàng)建代理對(duì)象,大概邏輯是。根據(jù)spring創(chuàng)建好一個(gè)aop切點(diǎn)BeanFactoryTransactionAttributeSourceAdvisor實(shí)例,遍歷當(dāng)前bean的class的方法對(duì)象,判斷方法上面的注解信息是否包含@Transactional,如果bean任何一個(gè)方法包含@Transactional注解信息,那么就是適配這個(gè)BeanFactoryTransactionAttributeSourceAdvisor切點(diǎn)。則需要?jiǎng)?chuàng)建代理對(duì)象,然后代理邏輯為我們管理事務(wù)開(kāi)閉邏輯。
          spring源碼中,在攔截bean的創(chuàng)建過(guò)程,尋找bean適配的切點(diǎn)時(shí),運(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的方法對(duì)象??
          ???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;??
          }??
          我們可以在上面的方法打斷點(diǎn),一步一步調(diào)試跟蹤代碼,最終上面的代碼還會(huì)調(diào)用如下方法來(lái)判斷。在下面的方法上斷點(diǎn),回頭看看方法調(diào)用堆棧也是不錯(cuò)的方式跟蹤。
          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)建代理對(duì)象

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

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

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

          第二種

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

          第三種

          事務(wù)方法內(nèi)部捕捉了異常,沒(méi)有拋出新的異常,導(dǎo)致事務(wù)操作不會(huì)進(jìn)行回滾。
          這種的話,可能我們比較常見(jiàn),問(wèn)題就出在代理邏輯中,我們先看看源碼里賣弄?jiǎng)討B(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.??
          ???????//開(kāi)啟事務(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??
          ??????????//異常時(shí),在catch邏輯中回滾事務(wù)??
          ?????????completeTransactionAfterThrowing(txInfo,?ex);??
          ?????????throw?ex;??
          ??????}??
          ??????finally?{??
          ?????????cleanupTransactionInfo(txInfo);??
          ??????}??
          ???????//提交事務(wù)??
          ??????commitTransactionAfterReturning(txInfo);??
          ??????return?retVal;??
          ???}??
          ??
          ???else?{??
          ?????//....................??
          ???}??
          }??
          所以看了上面的代碼就一目了然了,事務(wù)想要回滾,必須能夠在這里捕捉到異常才行,如果異常中途被捕捉掉,那么事務(wù)將不會(huì)回滾。
          總結(jié)了以上幾種情況。

          來(lái)源:blog.csdn.net/qq_20597727/article/

          details/84900994

          ——————END——————

          歡迎關(guān)注“Java引導(dǎo)者”,我們分享最有價(jià)值的Java的干貨文章,助力您成為有思想的Java開(kāi)發(fā)工程師!

          瀏覽 43
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <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>
                    狼友视频 - 首页 | 水蜜桃网站在线观看 | 国产日批v久久 | 国产无码性爱视频 | 免费黄色长视频 |