• @Async可以和@Transactional结合使用吗?



    前言

    在编写Spring在多线程环境下如何确保事务一致性时,我突然联想到@Async注解,心里就在盘算着@Async注解能否和@Transactional注解一起使用呢?

    当然,这里也是再看到了异步事务?关于异步@Async + 事务@Transactional的结合使用问题分析【享学Spring MVC】文章后,才想着对该问题作出一个彻底的研究,也是帮助其他小伙伴解开心头之惑。


    结论

    这里就不花费时间进行结论验证了,具体验证可以看下面这篇文章:

    异步事务?关于异步@Async + 事务@Transactional的结合使用问题分析【享学Spring MVC】

    我这边把上文中的结论整理一下,如下:

    • @Async注解的方法上,再标注@Transactional注解,事务依旧是生效的
    • 不同线程之间的事务完全隔离
    • 异步线程内仍是可以调用异步

    原理

    这里的原理只挑核心讲,想要彻底搞清楚原理,需要先把@Async注解实现原理和@Transactional注解的实现原理都弄清楚,Spring在多线程环境下如何确保事务一致性文中都已经将相关原理关联的阅读资源给出,不清楚的可以去查看一番。

    @Async和@Transactional注解都是通过Spring aop实现的,核心都是靠着关键的MethodInterceptor实现,@Async会给对应bean代理对象中放入一个AnnotationAsyncExecutionInterceptor拦截器,而@Transactional会给对应bean的代理对象中放入一个TransactionInterceptor拦截器。

    下面为了验证,我先给出一个使用例子:

    @Service
    @RequiredArgsConstructor
    public class TestService {
        private final IRoleService iRoleService;
        private final IAuthorityService iAuthorityService;
    
        /**
         * 测试方法
         */
        @Transactional
        @Async
        public void test() {
            iRoleService.removeById(1);
            iAuthorityService.removeById(1);
            throw new RuntimeException("我就要抛出异常");
        }
    }
    
    @SpringBootTest(classes = AuthenticationCenterMain.class)
    public class Test {
        @Resource
        private TestService testService;
    
        @SneakyThrows
        @org.junit.jupiter.api.Test
        public void test(){
          testService.test();
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    我们通过断点来查看一下为TestService生成的代理对象是什么模样:

    在这里插入图片描述
    可以看到是@Async注解提供的拦截器排在前面,而@Transactional注解提供的拦截器排在后面,因此可以知道,test方法事务过程的执行,是在@Async注解提供的某个异步线程内实现的。


    AsyncExecutionInterceptor拦截器提供的invoke方法如下:

    	public Object invoke(final MethodInvocation invocation) throws Throwable {
    		...
    		//决定使用哪一个异步线程池来执行当前标注有@Async注解的方法
    		AsyncTaskExecutor executor = determineAsyncExecutor(userDeclaredMethod);
    		...
            //将过滤器链的执行包装为一个Callable任务 
    		Callable<Object> task = () -> {
    			try {
    			   //过滤器链中此时还有一个TransactionInterceptor拦截器没有执行
    			   //该拦截器执行完后,最终执行目标方法
    				Object result = invocation.proceed();
    				if (result instanceof Future) {
    					return ((Future<?>) result).get();
    				}
    			}
    			catch (ExecutionException ex) {
    				handleError(ex.getCause(), userDeclaredMethod, invocation.getArguments());
    			}
    			catch (Throwable ex) {
    				handleError(ex, userDeclaredMethod, invocation.getArguments());
    			}
    			return null;
    		};
            //提交任务到线程池执行
    		return doSubmit(task, executor, invocation.getMethod().getReturnType());
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    TransactionInterceptor提供的invoke方法源码就不分析了,在Spring在多线程环境下如何确保事务一致性一文已经进行了分析。


    拦截器执行先后顺序问题

    @Async注解有关的拦截器实现了Ordered接口:
    在这里插入图片描述
    并且返回的是最高优先级:
    在这里插入图片描述
    大部分常用的Advisor都继承了AbstractPointCutAdvisor,因此都实现了getOrder接口,该接口实现中会判断对应advisor内部的advice是否实现了Ordered接口,如果实现了,那么advisor的优先级取决于内部的advice,否则默认返回最低优先级。

    因此@Async注解对应的advisor最终返回的是最高排序优先级。
    在这里插入图片描述

    当然,如果我们手动设置了advisor的优先级,那么会被优先选择,但是默认情况下大部分常用的advisor没有手动调用setOrder设置自己的优先级

    advisor排序的地方如下:
    在这里插入图片描述
    自动代理创建器在获取能应用到当前bean上的advisor时,最后会进行排序,自动代理创建器本身也是一个BeanPostProcessor,通过bean的生命周期回调接口,尝试返回一个代理对象。

    结论: AsyncExecutionInterceptor拦截器总是能够确保在其他拦截器之前执行,避免事务拦截器先执行,而异步拦截器后执行,导致错误发生。

    事务拦截器执行过程中,会将事务相关资源存放到threadlocal中,因此事务执行过程必须确保在一个线程内完成


    小结

    到此,我相信各位也基本清楚了@Async和@Transactional的关系了,本文比较简短,如果各位还有什么问题,可以在评论区提出。

  • 相关阅读:
    (十)Docker的基本操作
    lua执行出错attempt to index global ‘ngx‘ (a nil value)[已解决]
    Camera-MTK OpenCamera时序以及耗时
    【中国是部金融史-读后感】
    java如何进阶?
    升级ios16后iphone无法识别SIM?一招解决这个问题!
    k8s核心概念
    Azure 机器学习 - 机器学习中的企业安全和治理
    怎么移除pdf文件编辑限制,有哪些方法?
    spring5.0源码解析 从源码角度分析 advice 的执行顺序 aop 05
  • 原文地址:https://blog.csdn.net/m0_53157173/article/details/127435336