导致@Transactional失败的常见场景有五种:非public修饰的方法;timeout超时设置太小;在代码中使用try/catch来处理异常;在类内部调用@Transactional方法;数据库不支持事务。很多人只知其所以然,不知其所以然。这就像只谈恋爱不结婚一样,是不能接受的。那么在本文中,我们将讨论交易失败背后的原因是什么?以上五种场景中,第二种(timeout超时设置太小)和第五种(数据库不支持事务)比较容易理解,这里不赘述,重点介绍本文中的其他三种情况。1、非public修饰的方法在非public修饰的方法上,即使加上了@Transactional,事务仍然不会生效。原因是@Transactional是SpringAOP实现的,而SpringAOP是通过动态代理实现的,@Transactional会在生成代理时进行判断。如果方法是非public修饰的方法,则不会生成代理对象,也就没有办法自动执行事务。其部分源代码如下://非公共方法,设置为nullif(allowPublicMethodsOnly()&&!Modifier.isPublic(method.getModifiers())){returnnull;}//后面的代码省略....}2.try/catch导致事务失败@Transactional的执行过程是:@Transactional会在方法执行前自动打开事务;方法执行成功后,会自动提交交易;如果方法在执行过程中,如果发生异常,会自动回滚事务。但是,如果在方法中加入try/catch,事务不会自动回滚。怎么了?出现这个问题的主要原因与@Transactional注解的实现有关。其部分源码如下:protectedObjectinvokeWithinTransaction(Methodmethod,Class>targetClass,finalInvocationCallbackinvocation)throwsThrowable{//如果事务属性为null,则该方法是非事务性的。finalTransactionAttributetxAttr=getTransactionAttributeSource().getTransactionAttribute(方法,targetClass);最终PlatformTransactionManagertm=determineTransactionManager(txAttr);finalStringjoinpointIdentification=methodIdentification(methodClass)|Attrx!(tminstanceofCallbackPreferringPlatformTransactionManager)){//使用getTransaction和提交/回滚调用的标准事务划分。//自动开启交易TransactionInfotxInfo=createTransactionIfNecessary(tm,txAttr,joinpointIdentification);对象retVal=null;try{//这是一个绕过建议:Inv确定链中的下一个拦截器。//这通常会导致调用目标对象。//反射调用业务方法retVal=invocation.proceedWithInvocation();}catch(Throwableex){//目标调用异常//异常在catch逻辑中,事务自动回滚completeTransactionAfterThrowing(txInfo,ex);扔前;}最后{cleanupTransactionInfo(txInfo);}//自动提交事务commitTransactionAfterReturning(txInfo);返回值;}else{//.....}}从上面的实现源码可以看出,当执行的方法出现异常时,@Transactional可以感知到,然后执行事务回滚,而当开发者添加自己try/catch,@Transactional不会感知到异常,所以不会触发事务的自动回滚,这就是为什么当@Transactional遇到try/catch时,不会自动回滚(事务)3、调用类中使用的@Transactional方法在类内部调用@Transactional修饰的方法时,事务不会生效,如下代码所示:@RequestMapping("/save")publicintsaveMapping(UserInfouserInfo){returnsave(userInfo);}@Transactionalpublicintsave(UserInfouserInfo){//非空检查if(userInfo==null||!StringUtils.hasLength(userInfo.getUsername())||!StringUtils.hasLength(userInfo.getPassword()))返回0;intresult=userService.save(userInfo);整数=10/0;//这里设置异常returnresult;}即使上面的代码在添加用户后遇到异常,程序也没有返回Roll,这是因为@Transactional是基于SpringAOP实现的,而SpringAOP是基于动态实现的代理,而调用类内部的方法时,不是通过代理对象,而是通过this对象,所以绕过了代理对象,交易就失效了。总结一下,非public修饰的方法是在@Transactional实现的时候判断的。如果是非公开的,则不会生成代理对象,因此交易无效;而调用类内部@Transactional修饰的方法时,也是因为代理对象没有调用成功,方法是通过this调用的,所以交易也是无效的;@Transactional在遇到开发者定义的try/catch时也会失败,因为@Transactional只有在感知到异常时才会自动回滚(transaction)。但是如果用户自定义了try/catch,那么@Transactional是不会感知到异常的,所以事务不会自动回滚。参考&鸣谢blog.csdn.net/qq_20597727/article/details/84900994判断是非在自己,听别人说,得失看数字。公众号:Java面试真题分析面试合集:https://gitee.com/mydb/interview
