TransactionalInvalidation场景第一类Transactional注解的注解方法修饰符是non-public,@Transactional注解将不起作用。例如,下面的代码定义了一个错误的@Transactional注解实现,修改了一个默认的accessor方法:20,30));if(re>0){thrownewNeedToInterceptException("needintercept");}testMapper.insert(newTest(210,20,30));}}在同一个包中,新建一个access调用对象。@ComponentpublicclassInvokcationService{@ResourceprivateTestServiceImpltestService;publicvoidinvokeInsertTestWrongModifier(){//调用@Transactional标注的默认访问符方法testService.insertTestWrongModifier();}}测试用例:@RunWith(SpringRunner.class)@SpringBootTestpublicclassDemoApplicationTests{@ResourceInvokcationServiceinvokcationService;@TestpublicvoidtestInvoke(){invokcationService.invokeInsertTestWrongModifier();}}上面访问的方法,事务没有开启,所以当方法抛出异常时,testMapper.insert(newTest(10,20,30));操作不会回滚。如果将TestServiceImpl#insertTestWrongModifier方法改成public,事务就会正常打开,testMapper.insert(newTest(10,20,30));将被回滚。第二种失败场景是调用类内部的@Transactional注解方法。在这种情况下,交易将不会打开。示例代码如下,设置内部调用:/***@authorzhoujy**/@ComponentpublicclassTestServiceImplimplementsTestService{@ResourceTestMappertestMapper;@TransactionalpublicvoidinsertTestInnerInvoke(){//普通public修饰事务方法intre=testMapper.insert(newTest(10,20,30));if(re>0){thrownewNeedToInterceptException("needintercept");}testMapper.insert(newTest(210,20,30));}publicvoidtestInnerInvoke(){//类内部调用标记的方法通过@Transactional。insertTestInnerInvoke();}}测试用例:@RunWith(SpringRunner.class)@SpringBootTestpublicclassDemoApplicationTests{@ResourceTestServiceImpltestService;/***测试内部调用@Transactional注解方法*/@TestpublicvoidtestInnerInvoke(){//测试外部调用事务方法是否为normal//testService.insertTestInnerInvoke();//测试内部调用事务方法是否正常journey,testMapper.insert(newTest(10,20,30))操作会回滚;然后运行另一个测试用例,调用一个方法调用类内部@Transactional标记的事务方法,运行结果是事务不会正常打开,testMapper.insert(newTest(10,20,30))操作将保存到数据库中,不会回滚。第三种失败场景,事务方法内部捕获了异常,没有抛出新的异常,所以事务操作不会回滚。示例代码如下。/***@authorzhoujy**/@ComponentpublicclassTestServiceImplimplementsTestService{@ResourceTestMappertestMapper;@TransactionalpublicvoidinsertTestCatchException(){try{intre=testMapper.insert(newTest(10,20,30));if(re>0){//运行时抛出ExceptionthrownewNeedToInterceptException("needintercept");}testMapper.insert(newTest(210,20,30));}catch(Exceptione){System.out.println("icatchexception");}}}测试用例代码如下.@RunWith(SpringRunner.class)@SpringBootTestpublicclassDemoApplicationTests{@ResourceTestServiceImpltestService;@TestpublicvoidtestCatchException(){testService.insertTestCatchException();}}运行测试用例发现虽然抛出了异常,但是异常被捕获了,并没有抛出方法,testMapper.insert(newTest(210,20,30))操作没有回滚。以上三点是导致@Transactional注解不起作用,@Transactional注解失效的主要原因。下面结合spring中的@Transactional注解实现@Transactional注解为什么不起作用的源码分析。@Transactional注解不起作用原理分析第一种场景分析当@Transactional注解将方法修饰符注解为non-public时,@Transactional注解将不起作用。这里分析的原因是@Transactional是基于动态代理实现的。在实现原理中分析了@Transactional注解的实现方法。在bean初始化过程中,为包含@Transactional注释的bean实例创建一个代理对象。这里有一个春季扫描。@Transactional注解信息的过程,不幸的是,在源码中有体现。如果标有@Transactional的方法的修饰符不是public,那么默认方法的@Transactional信息为空,则不会为bean创建或不使用代理对象。方法使代理调用@Transactional注解实现原理,介绍如何判断一个bean是否创建代理对象,大致逻辑是。根据spring创建一个aop切入点BeanFactoryTransactionAttributeSourceAdvisor实例,遍历当前bean类的方法对象,判断方法上的注解信息是否包含@Transactional,如果bean的任何一个方法包含@Transactional注解信息,则为适配了这个BeanFactoryTransactionAttributeSourceAdvisorpoint-cuttingpoint。你需要创建一个代理对象,然后代理逻辑为我们管理事务开启和关闭逻辑。在spring源码中,在拦截bean的创建过程,寻找bean适配的切入点时,通过下面的方法找到方法上的@Transactional信息。如果有,说明切点BeanFactoryTransactionAttributeSourceAdvisor可以应用(canApply)到bean中,AopUtils#canApply(org.springframework.aop.Pointcut,java.lang.Class>,boolean)publicstaticbooleancanApply(Pointcutpc,Class>targetClass,booleanhasIntroductions){Assert.notNull(pc,"Pointcutmustnotbenull");if(!pc.getClassFilter().matches(targetClass)){returnfalse;}MethodMatchermethodMatcher=pc.getMethodMatcher();if(methodMatcher==MethodMatcher.TRUE){//Noneedtoiteratethemethodsifwe'rematchinganymethodanyway...returntrue;}IntroductionAullwareMethodMatcherintroodductionA;if(methodMatcherinstanceofIntroductionAwareMethodMatcher){introductionAwareMethodMatcher=(IntroductionAwareMethodMatcher)methodMatcher;}//遍历类方法对象Set>classes=newLinkedHashSet>(ClassUtils.getAllInterfacesForClassAsSet(targetClass));classes.add(targetClass);for(Class>clazz:classes){Method[]methods=ReflectionUtils.getAllDeclaredMethods(clazz);for(Methodmethod:methods){if((introductionAwareMethodMatcher!=null&&introductionAwareMethodMatcher.matches(method,targetClass,hasIntroductions))||//适配查询方法上的@Transactional注解信息methodMatcher.matches(method,targetClass)){returntrue;}}}returnfalse;}我们可以打破上面的方法,一步步调试跟踪代码,最后就是上面的代码还会调用下面的方法,判断断点在后面的方法上,回头看方法调用栈也是一个很好的追踪方式。AbstractFallbackTransactionAttributeSource#getTransactionAttributeAbstractFallbackTransactionAttributeSource#computeTransactionAttributeprotectedTransactionAttributecomputeTransactionAttribute(Methodmethod,Class>targetClass){//Don’tallowno-publicmethodsasrequired.//非公共方法,返回@Transactional信息一律是nullif(allowPublicMethodsOnly()&&!Modifier.isPublic())){returnnull;}//后面省略...}不创建代理对象所以,如果所有方法上的修饰符都是非public的,那么就不会创建代理对象。以一开始的测试代码为例,如果normal修饰符的testService是下图中cglib创建的代理对象。如果类中的方法是非公开的,那么它就不是代理对象。考虑一种没有代理调用的情况,如下代码所示。两种方法都使用@Transactional注解进行注解,但一个有public修饰符,另一个没有。如果我们能预见到这种情况,我们肯定会创建一个代理对象,因为至少有一个带有public修饰符的@Transactional注解注解方法。创建代理对象后,insertTestWrongModifier会启动事务吗?答案是不。/***@authorzhoujy**/@ComponentpublicclassTestServiceImplimplementsTestService{@ResourceTestMappertestMapper;@Override@TransactionalpublicvoidinsertTest(){intre=testMapper.insert(newTest(10,20,30));if(re>0){thrownewNeedToInterceptException("needintercept");}testMapper.insert(newTest(210,20,30));}@TransactionalvoidinsertTestWrongModifier(){intre=testMapper.insert(newTest(10,20,30));if(re>0){thrownewNeedToInterceptException("needintercept");}testMapper.insert(newTest(210,20,30));}}原因是动态代理对象进行代理逻辑调用时,在cglibCglibAopProxy.DynamicAdvisedInterceptor#创建的代理对象的拦截函数中#intercept,有一个逻辑如下,目的是获取当前被代理对象需要执行的当前方法适配的aop逻辑。List