一、@Transactional 与 @Async 可以同时使用吗
首先我们还是先写个 demo 看看运行结果。
demo 地址:https://github.com/zuiyu-main/EncryptDemo/tree/mysql-transactional-async
代码如下,使用注入的方式调用 TestService#test 方法。TestService#test 方法上同时使用 @Async 与 @Transactional 注解标注。
注意:需要在启动类中加入@EnableAsync注解,不了解的可以看下上一篇 @Async 原理。
让我们先看下运行结果。
通过上图可以知道,方法已经在异步线程中执行,方法的异常也已经抛出,数据库中也没有插入对应的数据。得出结论,@Transactional 可以与 @Async 注解同时使用,且事务可以生效。
这里提前透漏一个知识点把,大家可以把断点打在TransactionAspectSupport#invokeWithinTransaction()方法中243行位置处,判断代码是走245行的回滚逻辑还是258行的提交逻辑。
在或者在PlatformTransactionManager接口的 commit 或者 rollback 打上断点,看执行哪个方法。
二、@Transactional 注解原理
上一篇文章中说过了 @Async 注解的原理,还没看过的小伙伴可以过去看一眼哦。@Transactional 注解的工作原理也类似,都是 AOP 实现。
在哪找入口呢,@Async 注解的学习过程中,我们是从注解入手的,@Transactional 同样也可以,如下图所示。
所以上文中提到验证事务是否生效的方式中我就是从这受启发的。
不过今天我们看另一种方式,就是通过断点查看调用栈的方式。
这种方式有个缺点,就是假如事务是没有生效的,也就是说没有被 spring 代理,此时是无法达到我们的预期的。
所以有的时候看事务有没有生效,也可以通过这个方式。
首先在我们的异步方法第一行打入断点。
通过这一个断点,我们就可以知道两个消息。
- 代码1处,使用了Async异步执行,看了上篇文章@Async原理的一看就知道 @Async 注解生效了。
- 代码2处,第一处发现Transaction字眼的地方,所以也可以说明,我们这个方法已经被 spring 的事务管理起来了,而事务的入口就在这TransactionInterceptor#invoke,在该方法内部又调用了 TransactionAspectSupport#invokeWithinTransaction 。
进入到方法内部,需要注意的是 tas.getTransactionAttribute()。如果这个 transaction attribute 是空,说明这个方法是没有被事务管理的。
在 tas.getTransactionAttribute() 中,执行的是 AbstractFallbackTransactionAttributeSource 下的 getTransactionAttribute 方法,而在 getTransactionAttribute 方法中有一行 computeTransactionAttribute 方法,在该方法中有如下这样一行代码,这也是事务失效场景之一的 priviate 方法不支持事务。
事务失效场景:private 方法不支持事务。
如果你在 debug 时没有拦住该方法,需要重启,在程序启动时会缓存该方法的事务属性。
我们继续回到 TransactionAspectSupport#invokeWithinTransaction 方法,继续往下走。
在回滚事务的方法中,进行了判断,这就是另一种事务失效的场景,异常不匹配。所以在使用事务注解时一定要注意。
事务失效场景:异常类型不匹配。
当我们没有指定异常类型时,默认只对 RuntimeException 和 Error 有效。
所以,我们总结一下,与 一文搞懂@Async 原理一起阅读更佳。
- 程序启动之后为加了@Transactional 注解的方法缓存事务相关属性。
- 当方法中同时使用 @Async 与 @Transactional 注解时,@Async的优先级更高,会在 @Async 异步的线程中进行事务管理,当发生异常时也是可以回滚的。
现在我们知道了 @Transactional 与 @Async 注解的原理,那么当他们两个互不相干的方法互相调用还会生效吗?
只验证注入方式调用的形式。
三、@Transactional 调用 @Async
结论:test方法中事务生效,异步方法 test2 事务不生效。
异步方法 test2 上也加入事务注解
结论:test方法与 test2方法两个独立的事务,互不相关。
四、@Async 调用 @Transactional
结论:test 方法会因为 test2 方法执行报错而异常终止,test2 方法事务回滚。
如果我们把 test 方法中插入数据的代码移动到调用 test2 方法之前。
结论:test 方法中数据插入成功,test2 数据插入失败,事务回滚。
总结
我们在上一篇文章中学习了 @Async 注解的原理,今天看了 @Transactional 注解的原理。
通过写代码的形式,验证了:
- 当方法中同时使用 @Transactional 与 @Async 时,事务是可以生效的。
- @Transactional 调用 @Async 的方式,异步方法的事务是无法生效的。
- @Async 调用 @Transaction 的方式,异步方法事务是可以生效的,需要注意的是调用方也是没有事务管理的。