如何在数据库事务提交成功后进行异步操作

原文链接

问题


业务场景

业务需求上经常会有一些边缘操作,比如主流程操作A:用户报名课程操作入库,边缘操作B:发送邮件或短信通知。

业务要求

  • 操作A操作数据库失败后,事务回滚,那么操作B不能执行。(失败后也可以重新进行自调度)
  • 操作A执行成功后,操作B也必须执行成功

如何实现

  • 普通的执行A,之后执行B,是可以满足要求1,对于要求2通常需要设计补偿的操作
  • 一般边缘的操作,通常会设置成为异步的,以提升性能,比如发送MQ,业务系统负责事务成功后消息发送成功,然后接收系统负责保证通知成功完成

本文内容


如何在spring事务提交之后进行异步操作,这些异步操作必须得在该事务成功提交后才执行,回滚则不执行。

要点


  • 如何在spring事务提交之后操作
  • 如何把操作异步化

实现方案


使用TransactionSynchronizationManager在事务提交之后操作

public void insert(TechBook techBook){
        bookMapper.insert(techBook);
       // send after tx commit but is async
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
            @Override
            public void afterCommit() {
                System.out.println("send email after transaction commit...");
            }
        }
       );
        ThreadLocalRandom random = ThreadLocalRandom.current();
        if(random.nextInt() % 2 ==0){
            throw new RuntimeException("test email transaction");
        }
        System.out.println("service end");
    }

该方法就可以实现在事务提交之后进行操作

操作异步化

使用mq或线程池来进行异步,比如使用线程池:

private final ExecutorService executorService = Executors.newFixedThreadPool(5);
    public void insert(TechBook techBook){
        bookMapper.insert(techBook);

//        send after tx commit but is async
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
            @Override
            public void afterCommit() {
                executorService.submit(new Runnable() {
                    @Override
                    public void run() {
                        System.out.println("send email after transaction commit...");
                        try {
                            Thread.sleep(10*1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println("complete send email after transaction commit...");
                    }
                });
            }
        }
        );

//        async work but tx not work, execute even when tx is rollback
//        asyncService.executeAfterTxComplete();

        ThreadLocalRandom random = ThreadLocalRandom.current();
        if(random.nextInt() % 2 ==0){
            throw new RuntimeException("test email transaction");
        }
        System.out.println("service end");
    }

封装以上两步

对于第二步来说,如果这类方法比较多的话,则写起来重复性太多,因而,抽象出来如下:
这里改造了azagorneanu的代码:

public interface AfterCommitExecutor extends Executor {
}

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@Component
public class AfterCommitExecutorImpl extends TransactionSynchronizationAdapter implements AfterCommitExecutor {
    private static final Logger LOGGER = LoggerFactory.getLogger(AfterCommitExecutorImpl.class);
    private static final ThreadLocal<List<Runnable>> RUNNABLES = new ThreadLocal<List<Runnable>>();
    private ExecutorService threadPool = Executors.newFixedThreadPool(5);

    @Override
    public void execute(Runnable runnable) {
        LOGGER.info("Submitting new runnable {} to run after commit", runnable);
        if (!TransactionSynchronizationManager.isSynchronizationActive()) {
            LOGGER.info("Transaction synchronization is NOT ACTIVE. Executing right now runnable {}", runnable);
            runnable.run();
            return;
        }
        List<Runnable> threadRunnables = RUNNABLES.get();
        if (threadRunnables == null) {
            threadRunnables = new ArrayList<Runnable>();
            RUNNABLES.set(threadRunnables);
            TransactionSynchronizationManager.registerSynchronization(this);
        }
        threadRunnables.add(runnable);
    }

    @Override
    public void afterCommit() {
        List<Runnable> threadRunnables = RUNNABLES.get();
        LOGGER.info("Transaction successfully committed, executing {} runnables", threadRunnables.size());
        for (int i = 0; i < threadRunnables.size(); i++) {
            Runnable runnable = threadRunnables.get(i);
            LOGGER.info("Executing runnable {}", runnable);
            try {
                threadPool.execute(runnable);
            } catch (RuntimeException e) {
                LOGGER.error("Failed to execute runnable " + runnable, e);
            }
        }
    }

    @Override
    public void afterCompletion(int status) {
        LOGGER.info("Transaction completed with status {}", status == STATUS_COMMITTED ? "COMMITTED" : "ROLLED_BACK");
        RUNNABLES.remove();
    }

}
public void insert(TechBook techBook){
        bookMapper.insert(techBook);

//        send after tx commit but is async
//        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
//            @Override
//            public void afterCommit() {
//                executorService.submit(new Runnable() {
//                    @Override
//                    public void run() {
//                        System.out.println("send email after transaction commit...");
//                        try {
//                            Thread.sleep(10*1000);
//                        } catch (InterruptedException e) {
//                            e.printStackTrace();
//                        }
//                        System.out.println("complete send email after transaction commit...");
//                    }
//                });
//            }
//        }
//        );

        //send after tx commit and is async
        afterCommitExecutor.execute(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(5*1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("send email after transactioin commit");
            }
        });

//        async work but tx not work, execute even when tx is rollback
//        asyncService.executeAfterTxComplete();

        ThreadLocalRandom random = ThreadLocalRandom.current();
        if(random.nextInt() % 2 ==0){
            throw new RuntimeException("test email transaction");
        }
        System.out.println("service end");
    }

关于Spring的Async


spring为了方便应用使用线程池进行异步化,默认提供了@Async注解,可以整个app使用该线程池,而且只要一个@Async注解在方法上面即可,省去重复的submit操作。关于async要注意的几点:

1、async的配置

<context:component-scan base-package="com.yami" />
   <!--配置@Async注解使用的线程池,这里的id随便命名,最后在task:annotation-driven executor= 指定上就可以-->
    <task:executor id="myExecutor" pool-size="5"/>
    <task:annotation-driven executor="myExecutor" />

这个必须配置在root context里头,而且web context不能扫描controller层外的注解,否则会覆盖掉。

<context:component-scan base-package="com.yami.web.controller"/>
<mvc:annotation-driven/>

2、async的调用问题

async方法的调用,不能由同类方法内部调用,否则拦截不生效,这是spring默认的拦截问题,必须在其他类里头调用另一个类中带有async的注解方法,才能起到异步效果。

3、事务问题

async方法如果也开始事务的话,要注意事务传播以及事务开销的问题。而且在async方法里头使用如上的TransactionSynchronizationManager.registerSynchronization不起作用,值得注意。

<think>我们被要求回答:在Spring Boot中如何实现事务提交后执行其他操作 根据引用的内容,特别是引用[3]和引用[4],我们可以找到两种方法: 方法1:使用TransactionSynchronizationManager注册一个事务同步回调(引用[3]) 方法2:使用一个自定义的TransactionAfterCommitExecutor(引用[4]) 下面我们将详细说明这两种方法。 方法1:使用TransactionSynchronizationManager 步骤: 1. 检查当前是否存在活动的事务(TransactionSynchronizationManager.isActualTransactionActive()) 2. 如果存在,注册一个TransactionSynchronizationAdapter,并重写其afterCommit方法,在该方法中执行需要在事务提交后执行的操作。 示例代码(参考引用[3]): if (TransactionSynchronizationManager.isActualTransactionActive()) { TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() { @Override public void afterCommit() { // 事务提交后执行的操作 log.info("当前事务提交,执行业务逻辑"); // do something } }); } else { // 没有事务,直接执行 log.info("当前无事务,直接执行业务逻辑"); // do something } 注意:TransactionSynchronizationAdapter是一个抽象类,通常我们只重写需要的方法(如afterCommit)。另外,在Spring 5.0以后,可以使用TransactionSynchronization接口并直接使用default方法,但使用适配器类仍然是一种常见做法。 方法2:使用自定义的TransactionAfterCommitExecutor(参考引用[4]) 这种方法封装了事务提交后的执行逻辑,通过一个执行器来执行。在引用[4]中,使用了afterCommitExecutor.execute(...)来执行一个在事务提交后的操作。 步骤: 1. 创建一个实现了TransactionAfterCommitExecutor的Bean,它通常使用TransactionSynchronizationManager来注册回调。 2. 在需要的地方注入该Bean,并在事务方法中使用它。 引用[4]中的示例: 在Service中注入了TransactionAfterCommitExecutor,然后在事务方法中调用: afterCommitExecutor.execute(() -> ApplicationContextUtil.publishEvent(new UserEvent(user, user.getId()))); 我们可以自己实现一个简单的TransactionAfterCommitExecutor: @Component public class TransactionAfterCommitExecutor { public void execute(Runnable runnable) { if (TransactionSynchronizationManager.isActualTransactionActive()) { TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() { @Override public void afterCommit() { runnable.run(); } }); } else { runnable.run(); } } } 这样,在事务方法中就可以直接使用这个执行器来提交需要在事务提交后执行的任务。 注意事项: - 在事务提交后执行的操作,如果操作本身需要访问数据库,那么它将在新的事务上下文中执行(因为原事务已经提交),所以需要确保新操作事务管理(例如,可以单独使用一个事务)。 - 如果事务回滚,则不会执行afterCommit中的操作。 因此,根据需求,我们可以选择其中一种方法来实现。 回答结构: 1. 介绍需求背景 2. 提供两种方法(方法1和方法2)并给出代码示例 3. 注意事项 生成相关问题: 1. 在事务提交后执行的操作如果发生异常会怎么样? 2. 如何在事务回滚后执行某些操作? 3. Spring Boot中如何保证事务提交后的操作异步执行的?</think>### 如何在 Spring Boot 中实现事务提交后执行其他操作 在 Spring Boot 中,可通过以下两种方式实现事务提交后执行操作: --- #### **方法 1:使用 `TransactionSynchronizationManager`(原生支持)** 通过注册事务同步回调,在事务提交后触发自定义逻辑: ```java import org.springframework.transaction.support.TransactionSynchronization; import org.springframework.transaction.support.TransactionSynchronizationManager; @Service public class UserService { @Transactional public void createUser(User user) { // 1. 业务操作(如保存用户) userRepository.save(user); // 2. 注册事务提交后的回调 if (TransactionSynchronizationManager.isActualTransactionActive()) { TransactionSynchronizationManager.registerSynchronization( new TransactionSynchronization() { @Override public void afterCommit() { // 事务提交后执行的操作 sendNotification(user); // 例如发送通知 log.info("事务提交,执行后续操作"); } } ); } } } ``` **原理**: Spring 的事务管理器通过 `TransactionSynchronization` 接口提供生命周期钩子。`afterCommit()` 方法会在事务成功提交后触发[^3]。 --- #### **方法 2:自定义事务后置执行器(封装复用)** 封装通用组件,简化代码: ```java @Component public class TransactionAfterCommitExecutor { public void execute(Runnable task) { if (TransactionSynchronizationManager.isActualTransactionActive()) { TransactionSynchronizationManager.registerSynchronization( new TransactionSynchronizationAdapter() { @Override public void afterCommit() { task.run(); } } ); } else { task.run(); // 无事务时直接执行 } } } // 在 Service 中使用 @Service @RequiredArgsConstructor public class OrderService { private final TransactionAfterCommitExecutor afterCommitExecutor; @Transactional public void createOrder(Order order) { orderRepository.save(order); // 提交后生成报表 afterCommitExecutor.execute(() -> reportService.generateReport(order)); } } ``` **优势**: - 代码复用,避免重复注册回调[^4]。 - 支持无事务场景的降级处理。 --- #### **注意事项** 1. **执行上下文**: `afterCommit()` 中的操作**不在原事务内**,需独立处理事务(如添加 `@Transactional(propagation = Propagation.REQUIRES_NEW)`)。 2. **异常处理**: 若 `afterCommit()` 中的逻辑抛出异常,**不会回滚已提交事务**,需单独捕获处理。 3. **异步优化**: 耗时操作建议结合 `@Async` 实现异步执行,避免阻塞主线程: ```java afterCommitExecutor.execute(() -> { asyncService.sendEmail(user); // 异步方法 }); ``` --- ### 相关问题 1. **事务提交后的操作抛出异常会影响已提交的数据吗?** 不会。原事务提交,后续操作需独立处理异常[^3]。 2. **如何实现在事务回滚后执行特定逻辑?** 可重写 `TransactionSynchronization` 的 `afterCompletion()` 方法,根据状态码 `STATUS_ROLLED_BACK` 处理[^3]。 3. **非 Spring 管理的线程中能否使用 `TransactionSynchronizationManager`?** 不能。该工具类依赖 ThreadLocal 存储事务上下文,需确保操作在 Spring 事务线程内[^1][^2]。 4. **事务提交后的操作如何参与分布式事务?** 需通过消息队列(如 RabbitMQ/Kafka)或分布式事务框架(如 Seata)保障最终一致性。 [^1]: 多线程事务管理:Spring Boot 实现全局事务回滚 [^2]: Spring Boot 中的事务是如何实现的?懂吗? [^3]: Spring 事务管理器:等事务提交之后执行方法 [^4]: 需要在事务提交后执行的操作 - Spring Boot 版
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值