SpringBoot 事务作用于异步方法或线程池踩过的坑

首先,我们的项目配置了全局事务,拦截的是ServiceImpl层中以add、save、insert、update等为方法名前缀的方法(主要拦截增删改操作)。场景是这样的,我们有好几个操作是先执行insert方法(同步方法),再执行saveMsg方法(异步方法),这两个方法都能被全局事务拦截到,事务的传播特性是Required,原则上来讲两个方法应该在同一个事物。一开始XxxServiceImpl中的代码大概是这样的:

   public void insertXxx() {

		// 先执行插入
		insert();

		// 中间会拼装一部分msg信息
		doSomething...

		// 然后调用 saveMsg
		saveMsg(Msg msg);
    }

saveMsg方法是用来拼装一条消息并存入数据库的,消息内容依赖于insert方法中插入的数据,但是有时候会出现消息内容错误的现象,最开始并没有怀疑是事务问题,以为是拼装消息的方法有bug,经过几次debug之后发现即使同一个操作这种问题也会偶现,所以排除了拼装消息这部分代码的bug。其实saveMsg方法本身并不是异步方法,没有加@Async这种注解,所以一开始也没有怀疑是事务的问题,但是这个方法中有个子方法A是在线程池中执行的,所以相当于是个异步方法,最开始saveMsg代码是这样的(作者不是我):

	// saveMsg本身并不是异步方法,是因为里面用到了线程池
    public void saveMsg(Msg msg) {
		//  A内部是拼装消息的代码,在线程池中执行的,相当于异步方法,A是在出现问题后才单独抽出来的
	    A(msg);
    }

于是后面就怀疑saveMsg方法没读到insert方法插入的数据,怀疑两个方法不在同一个事物,且有时候saveMag方法的事物比insert方法的事物先执行。于是我也查了一下相关资料,代码改成了这样:

    public void saveMsg(Msg msg) {
        // 这段代码的作用是保证A方法在前面的事物提交完之后再执行
        TransactionSynchronizationManager.registerSynchronization(
                new TransactionSynchronizationAdapter() {
                    @Override
                    public void afterCommit() {
                        A(msg);
                    }
                }
        );
    }

这样改了之后问题解决,也过了一段风平浪静的日子。终于在一个风和日丽的上午,新增一个功能:消息通知某某某,这个功能主要就是要调用saveMsg方法。开开心心的写完接口后,一测试就给我返报了个错,当时我那个懵逼呀。。。具体错误我忘记了,大概就是说在当前没有事务的情况下执行TransactionSynchronizationManager.registerSynchronization(…);这段代码有问题,看到报错我倒是很快反应过来,应该先判断是否存在事务。于是,又改了一版:

	public void saveMsg(Msg msg) {
        boolean synchronizationActive = TransactionSynchronizationManager.isSynchronizationActive();
        if (synchronizationActive) {
            TransactionSynchronizationManager.registerSynchronization(
                    new TransactionSynchronizationAdapter() {
                        @Override
                        public void afterCommit() {
                            A(msg);
                        }
                    }
            );
        } else {
            A(msg);
        }
    }

终于,问题解决,到写这篇博客的时候已经过去几个月,再也没有出现问题。
回过头来想,当时如果考虑全面一点,一开始就判断事务是否存活,也就不会出现第二次的问题,改第一个问题的时候,局限于调用saveMsg之前一定会有insert操作,根本没有想过后面会有这种直接调用saveMsg方法的,当然这也是受了网上别人也没写这个判断的影响,也许他们的场景不同,不过也算是一次成长吧。

发布了18 篇原创文章 · 获赞 25 · 访问量 2万+

猜你喜欢

转载自blog.csdn.net/oyc619491800/article/details/104466691