事务什么时候失效,你真的理解吗?相信很多小伙伴在面试过程经常会被面试官问到?@Transactional什么时候会失效?带着这个问题,我先从以下四个失效的例子,讲述失效的场景,先让我们初步有个大概的了解,然后再通过源码的解读,分析@Transactional失效的原因,从而避免以后在工作中踩坑。
@Transactional 失效场景
- 私有方法(访问权限修饰符为private的方法)
- 异常类型不匹配
- 同类非事务方法调用事务方法
- 多线程
示例1:私有方法
使用private修饰@Transactional注解的方法,由于idea会检查实现类方法的访问权限修饰符,所以此处不进行测试。
示例2:异常类型不匹配
这是由于我们在写@Transactional后没有设置回滚类型导致,这个在idea中会有黄色警告线标识-**方法【exceptionType】需要在Transactional注解指定rollbackFor或者在方法中显式的rollback。**提示我们指定回滚异常类型,避免产生意料之外的结果。
@Override
@Transactional
public void exceptionType(boolean isEnableRollbackException) throws IOException {
userMapper.insert(User.builder().name("cpz").age(18).email("cpz@qq.com").build());
if (isEnableRollbackException) {
throw new RuntimeException("RUNTIME Exception");
} else {
throw new IOException("IOE Exception");
}
}
代码解析:本例通过向数据库新增一条用户数据,然后抛出不同类型的异常(通过布尔类型参数isEnableRollbackException
控制),最后观察该条数据是否在数据库里,即可完成类型异常的测试。
示例3:同类非事务方法调用事务方法
这是由于@Transactional是基于 aop 实现的,而 aop 使用动态代理实现,通过代理直接调用方法,会在方法前后加上事务相关逻辑。而现在直接通过类内部方法调用,则不会在方法前后生成事务相关逻辑,自然而然事务也会失效。
@Override
public void internalMethod() {
this.insertUser();
throw new RuntimeException("Runtime Exception");
}
@Transactional
public void insertUser() {
userMapper.insert(User.builder().name("cpz").age(18).email("cpz@qq.com").build());
}
代码解析:通过声明事务方法insertUser
,而后使用同类方法internalMethod
直接调用,从而绕过动态代理对象的增强-开启事务,提交事务等逻辑。最后观察该条数据是否在数据库里,即可完成同类非事务方法调用事务方法的测试。
示例4:多线程
由于父子线程是相对独立的,因此它们的之间的事务不是同一个的,所以不管是父线程抛出异常还是在子线程中抛出异常,对于另外的线程是不受影响的。
@Override
public void multiThread(boolean isThrowFromParentThread) {
if (isThrowFromParentThread) {
new Thread(() -> {
userMapper.insert(User.builder().name("cpz").age(18).email("cpz@qq.com").build());
}).start();
throw new RuntimeException();
} else {
new Thread(() -> {
userMapper.insert(User.builder().name("cpz").age(18).email("cpz@qq.com").build());
throw new RuntimeException();
}).start();
}
}
代码解析:通过参数isThrowFromParentThread
控制是父子线程抛出异常。最后观察该条数据是否在数据库里,即可完成多线程方法的测试。
源码解读:
1、ReflectiveMethodInvocation#proceed()
首先会经过一个拦截器interceptorOrInterceptionAdvice(TransactionInterceptor)
,执行invoke(this)方法,this为ReflectiveMethodInvocation
对象实例,里面包含我们测试的代理对象,方法、参数等属性。
TransactionInterceptor#invoke(invocation)
2、执行方法之后会进入事务方法invokeWithinTransaction
,此处是我们事务的核心,包含:1、获取事务属性后开启事务、2、目标方法的调用、3、事务的回滚、4、事务的提交,对示例的事务失效问题会在这里解惑。
第一步获取事务属性会调用computeTransactionAttribute
方法完成属性的获取
里面会去判断是否为public修饰的方法,所以这也是示例1私有方法不行的原因
接下来看看第三步事务的回滚
回滚事务条件
而默认只有运行时异常(RuntimeException
)和错误(ERROR
)会回滚
所以示例2不配置rollbackFor = Exception.class
会出现异常类型不匹配情况从而导致事务失效。
这里介绍下异常类的层次
「学习交流」
可以扫下面二维码,关注「我的极简博客」公众号。
一直在追求思路的传递而非代码的COPY