前面两篇,聊到了使用 @Async实现异步调用,@Async与异步线程池、异步异常处理;这篇笔记将记录使用 @Transactional方法中如何解决调用异步方法实现异步,@Async如何解决异步事务 问题。
[TOC]
问题场景
场景一
Spring 的异步执行注解 @Async,在 @Transactional 的方法中调用这个方法的时候发现,不对劲,耗时的逻辑我已经加入到异步去做了,怎么接口请求的响应这么慢,赶紧看日志,懵X,加了异步注解,却没有异步执行。
场景二
在项目中用到 @Transactional 注解实现事务是必须滴,如果你还在用xml配置,那我只能说……。
但是有时候我们会发现在方法上加了@Transactional 注解却出现灵异事件,在方法内出现异常,数据还是插入到数据库,没有回滚,事务哪里去了,明明是加了的。
@Async与@Transactional
这两个注解的共同点,都是通过 Spring AOP 的代理实现功能的。而 Spring AOP 的底层实现就是动态代理,CGLIB 和 JDK 。Spring AOP 会根据具体的实现不同,采用不同的代理方式。
@Transactional方法中如何解决调用异步方法实现异步
当事务方法调用了同一个类中的异步方法时,异步注解@Async不起作用,@Async注解失效原因分析和解决方案
下面给出伪代码
1 | class C { |
原因:程序运行后,由于方法 A 被 @Transactional 修饰,所以 A() 会被代理类调用(AOP 将事务嵌到方法 A 上),而 A() 中又直接调用了 方法 B ,这时候 方法 B 就变成了方法 A 中的一个普通方法,没有没代理类调用,实现不了 @Async 的功能,所以异步才会失效。
简单来说就是:@Transactional 和 @Async 标记的方法 不允许被 ==同一个类中== 的其他方法 ==直接== 调用自己,同一类中直接调用则不会调用动态代理生成的ProxyClass(因为同一类中被调用方法的注解不被解析)
- @Transactional 调用 @Async(失效)(本文就是例子)
- @Transactional 调用 @Transactional(有效,其实是第一个 @Transactional 起的作用,参考事务默认的传播行为)
- @Async 调用 @Transactional(失效)
- @Async 调用 @Async(失效)
==三个小demo在 GitHub 上==
解决方法:
事务方法和异步方法不要在同一个类中(推荐)
如果非要在同一个类中的话,那就使用代理类来调用异步方法即可
在配置类(启动类也属于配置类)上加
@EnableAspectJAutoProxy(exposeProxy = true)
(暴露代理类)在事务方法体中使用
AopContext.currentProxy()
获得当前类的代理对象1
2
3// 获得当前代理对象
当前类 service = (当前类) AopContext.currentProxy();
log.info("当前对象的代理对象为:[{}]", service.getClass());伪代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18true) (exposeProxy =
// 启动类也属于配置类
class D {
}
class C {
function A {
print("A插入数据");
C service = (C)AopContext.currentProxy();
c.B();
}
function B {
print("B插入数据");
}
}
@Async如何解决异步事务
问题:事务方法调用异步方法时,怎么保证在异步方法中出现异常时,异步方法与事务方法中的事务都回滚。
如果在异步方法上加上 @Transactional 的话,是可以实现事务,但是由于是异步,线程不一样,所以他们两个不属于同一个事务, 不能保证异步方法出现异常,事务方法也跟着回滚。
伪代码如下:
1 | true) (exposeProxy = |
在异步方法抛异常后,异步方法的事务会回滚,但是事务方法的事务就没有回滚了,这不是我们想要的结果。那么我们应该怎么实现异步方法抛异常,异步方法事务回滚,事务方法事务也要回滚呢?
解决方法:
异步方法加 @Transactional 后,还需要结合 try...catch...finally...
使用。在方法体中 try 代码块
捕获异常,在 catch 代码块
中手动回滚事务,并把异常信息封装到 Future 对象
中,然后在 finally 代码块
中将 Future 对象
return 回主线程。这样,在主线程中通过判断 Future 对象
的对象类型从而决定是否抛出异常,如果 Future 对象
中封装的是异常对象,那么我们可以在主线程中直接将这个异常对象抛出(手动抛出异常的话事务则会自动回滚;我们也可以选择手动回滚,看需要吧),即可达到异步方法出现异常,事务方法收到 Future 对象
中的异常信息,加几步处理后就能使双方都事务回滚。这就解决了 异步事务 了(两个方法不属于同一个事务,但通过 Future 对象
实现异步方法一报错,双方都回滚的效果。)
伪代码如下:(想要解决异步事务,下面的姿势还算可以的)
注意: 由于异步方法要返回结果,所以在异步方法的catch
中只能手动抛异常
1 | true) (exposeProxy = |
环境/版本一览:
- 开发工具:Intellij IDEA 2018.2.2
- springboot: 2.0.6.RELEASE
- jdk:1.8.0_171
- maven:3.3.9
- mysql-connector-java:8.0.13
- druid-spring-boot-starter:1.1.9
- lombok:1.18.2
- spring-boot-starter-data-jpa:2.0.6.RELEASE
1、pom.xml
1 | <dependencies> |
2、application.yml
1 | spring: |
3、entity
Order.java
1 | package com.fatal.entity; |
Message.java
1 | package com.fatal.entity; |
4、dao
OrderRepository.java
1 | package com.fatal.dao.order; |
MessageRepository.java
1 | package com.fatal.dao.message; |
5、component
1 | package com.fatal.component; |
6、service
IOrderService.java
1 | package com.fatal.service; |
OrderServiceImpl.java
1 | package com.fatal.service.impl; |
7、Application
1 | package com.fatal; |
8、Test
1 | package com.fatal.test; |
显示
测试一
测试内容:事务方法内直接调用同一类中的异步方法(异步失效)。要求注掉 sendMessage() 中的 int i = 1/0;
运行 TransactionalTests.saveAndAsyncNoWithProxy();
显示
测试二
测试内容:事务方法内通过代理类调用同一类中的异步方法(异步生效)
运行 TransactionalTests.saveAndIdenticalClassAsyncWithProxy();
不打开 int i = 1/0; 测试 @Async是否起作用
显示
打开 int i = 1/0; 测试 @Transactionl + @Async 异步事务
显示
打开数据库,可以发现数据都回滚了
测试三
测试内容:事务方法内直接调用不同类中的异步方法(异步生效)
运行 TransactionalTests.saveAndDifferentClassAsyncWithProxy();
不打开 int i = 1/0; 测试 @Async是否起作用
显示
打开 int i = 1/0; 测试 @Transactionl + @Async 异步事务
显示
打开数据库,可以发现数据都回滚了
笔记
抛异常后还能return吗?
不能,但是我们可以用 try…catch…finally… ,在捕获到异常后,我们在 catch 代码块 中对异常进行处理,然后在 finally 代码块中我们 return 即可
@Transactional 标注的方法怎么手动回滚事务。
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
@Async 结合 @Transactional 使用,异步是有效果的。
Future 的返回值为 void 的话,则可用于只有单边操作数据库的。
总结
SpringBoot
的知识已经有前辈在我们之前探索了。比较喜欢的博主有:唐亚峰 | Battcn、方志朋的专栏、程序猿DD、纯洁的微笑。对这门技术感兴趣的可以去他们的博客逛逛。谢谢他们的分享~~
以上文章是我用来学习的Demo
,都是基于 SpringBoot2.x
版本。
源码地址: https://github.com/ynFatal/springboot2-learning/tree/master/chapter24_3