前言
在上一篇博文中,我们对@Transactional源码进行了解析,这一篇我们来结合源码,分析一下我们面试、开发中经常遇到的事务失效问题
前置知识点
- 如果类或者方法上存在@Transactional注解,则Spring会对原始对象进行动态代理。如果是cglib动态代理,则普通方法会进入DynamicAdvisedInterceptor拦截器,如果是jdk动态代理,则普通方法会进入JdkDynamicAopProxy拦截器
- 代理对象会查找可以作用于当前方法的advice(Interceptor的父接口),然后构成拦截器链,链式调用各个拦截器的invoke方法。@Transactional注解最终注入的拦截器为TransactionInterceptor,执行相关方法都会进入这个拦截器
有兴趣的小伙伴可以阅读我的上篇博文,可以帮助大家更好理解本篇文章
失效场景
1.访问修饰符
demo演示
@Transactional
private void methodA() {
saveData();
updateData();
}
源码解析
TransactionInterceptor#invoke
TransactionAspectSupport#invokeWithinTransactionAbstractFallbackTransactionAttributeSource#getTransactionAttribute
AbstractFallbackTransactionAttributeSource#computeTransactionAttribute
结论:@Transactional只能作用于public描述的普通方法
2.方法用final修饰
demo演示
@Transactional
public final void methodB() {
saveData();
updateData();
}
原因
含有@Transactional注解标注的方法或者类,会被InfrastructureAdvisorAutoProxyCreator这个BeanPostProcessor动态代理,普通public修饰的方法会经过拦截器链,然后执行其invoke方法。TransactionInterceptor的invoke方法会开启事务,但是final修饰的方法,不可以重写,不会进行方法增强,即不会执行TransactionInterceptor的invoke方法来开启事务
流程图
3.方法的自调用
demo演示
public void methodC() {
this.methodD();
}
@Transactional
public void methodD() {
saveData();
updateData();
}
这个this到底是什么?(以cglib动态代理为例)
存在两种情况有拦截器链和没有拦截器链
没有拦截器链
有拦截器链
我们得出结论
- 没有拦截器链执行methodProxy.invoke(target, args)
- 有拦截器链执行method.invoke(target, args)
CGLIB动态代理的FastClass机制
- *.invoke(target,args) : 执行父类的方法
- *.invoke(proxy,args) : 产生死循环
- proxyMethod.invokeSuper(target,args) : 父类转子类根据里氏替换原则,抛出类型转换异常
- proxyMethod.invokeSuper(proxy,args) : 子类方法(方法增强)
综上所述:不管有没有拦截器都会执行原始对象方法,this指的是目标对象
解决方法
1.获取代理对象
DynamicAdvisedInterceptor的intercept方法
如果我们将this.advised.exposeProxy这个属性设为true,就可以通过AopContext.currentProxy()获取代理对象
操作步骤
- 在配置类上加上@EnableAspectJAutoProxy注解,将exposeProxy属性设为true
- 改造代码
public void methodC() {
TransactionService proxy = (TransactionService) AopContext.currentProxy();
proxy.methodD();
}
PS : @EnableAspectJAutoProxy注解和@EnableTransactionManagement注解有很深的渊源,有兴趣的读者,可以阅读我的关于Spring AOP相关博文
2.实现ApplicationContextAware接口
package com.test.aop.service;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class TransactionService implements ApplicationContextAware {
// 注意:不需要加@Autowired注解,spring在相应阶段会执行setApplicationContext方法
private ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
// 没有@Transactional注解
public void methodC() {
TransactionService service = applicationContext.getBean(TransactionService.class);
service.methodD();
}
@Transactional
public void methodD() {
saveData();
updateData();
}
}
3.新建一个service,将methodD挪到新service里面
4.不匹配的传播行为
之前我根据源码整理的流程图,不同传播行为的搭配可能会抛出异常
5.异常问题
相关源码TransactionAspectSupport#invokeWithinTransaction
我们重点关注completeTransactionAfterThrowing方法
我们再关注RuleBasedTransactionAttribute的rollbackOn方法
@Transactional注解的rollbackFor、rollbackForClassName、noRollbackFor、noRollbackForClassName属性,后期会被解析成RollbackRuleAttribute对象
case1:@Transactional注解未指定rollbackFor、rollbackForClassName、noRollbackFor、noRollbackForClassName
当我们未指定上述四个属性,会调用super.rollbackOn的方法
默认情况下,事务只有在遇到RuntimeException异常或者Error的时候才会回滚
case2:@Transactional注解指定rollbackFor、rollbackForClassName、noRollbackFor、noRollbackForClassName
当我们指定了四个属性中的一个或者多个,就会被解析成RollbackRuleAttribute(NoRollbackRuleAttribute),最后通过getDepth方法获取winner,如果winner是RollbackRuleAttribute旧回滚,否则就提交事务
getDepth方法主要判断抛出的异常与指定的异常之间的关系
- 如果抛出异常和指定异常一致,则depth为0
- 如果抛出异常是指定异常子类,则depth加1(递归判断)
- 如果抛出异常为Throwable,则depth为-1
depth值越小(大于0),优先级越高
综上所述
- 当我们自己catch住异常,不会回滚事务 原因 : 当我们未抛出异常,不会执行completeTransactionAfterThrowing方法,更不会执行回滚操作
- 未指定rollbackFor、rollbackForClassName、noRollbackFor、noRollbackForClassName属性,但是抛出的不是RuntimeException(Error)异常 原因 : 默认情况下,事务只有在遇到RuntimeException异常或者Error的时候才会回滚
- 指定了rollbackFor、rollbackForClassName、noRollbackFor、noRollbackForClassName属性,但是winner是noRollbackFor、noRollbackForClassName属性指定的异常
- 指定了rollbackFor、rollbackForClassName、noRollbackFor、noRollbackForClassName属性,但是未推断出winner,异常也不是RuntimeException或Error
需要注意的是,如果事务里面存在嵌套事务,需要将嵌套事务的方法catch一下,不然嵌套事务抛出的异常也可能导致上层事务的回滚
6.多线程调用
demo演示
@Service
public class FirstService {
@Autowired
private SecondService secondService;
@Transactional
public void doMainAndAsync() {
saveData();
updateData();
new Thread(() -> secondService.doAsync()).start();
}
}
@Service
public class SecondService {
@Transactional
public void doAsync() {
System.out.println("doAsync");
}
}
相关源码SqlSessionInterceptor#invoke
Spring是通过事务同步管理器(TransactionSynchronizationManager)来管理事务的,每一个线程都有一个独立的副本,所以不同线程取出来的sqlSession是不一样的,所以不能回滚异步线程的事务
7.其他
表不支持事务 : myisam引擎的表不支持事务
未被Spring管理 : 类上遗漏@Service注解