SpringBoot事务异步调用引发的bug解决

 更新时间:2023年06月14日 11:10:37   作者:在下uptown  
本文主要介绍了SpringBoot事务异步调用引发的bug解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

前言

日常开发中有没有遇到这种场景,save一条数据后发起一次异步调用,举个例子,假设我们以mysql组件和xxl-job组件为例,创建一条数据导出任务,创建后默认启动任务。那么逻辑可能大致为三步。

  • 创建导出任务(DB.EXPORT_TASK)
  • 创建导出任务历史记录(DB.EXPORT_TASK_HISTORY)
  • 触发导出任务(XXL-JOB)

因为需要同时要创建导出任务和导出任务历史两条记录,所以代码中需要通常要添加事务

@Service
public class TaskService {
    @Transactional(rollbackFor = Exception.class)
    public String saveExportTask() {
        // 1. save export task
        // 2. save export task history 
        // 3. execute xxl-job
    }
}

外层controller层只需要调用service的方法即可

@RestController
public class TaskController {
    @Resource TaskService taskService;
    @PostMapping
    public String save() {
        taskService.saveExportTask();
    }
}

我们使用了xxl-job去触发任务是一个异步调用的过程,当xxl-job回调执行器去执行时可能需要根据job_id获取到导出任务的配置,通过查询db获取任务详情,比如导出地址了,导出规范等等。
看似非常和谐的场面,实际执行起来则会出现任务不存在的问题。问题的根源其实也很好理解,就是因为在异步方法里做了同步的事就会出现这种问题,当第一步没有执行完,第三步的回调方法已经到执行器了,也就是说一个任务还没存到数据库,执行这个任务时去数据库查该任务的明细肯定会报任务不存在异常了。
那么如何解决呢。

代码拆分

最简单的一个方案,web应用通常划分为controller、service、dao层那么几层,业务逻辑按规范写在service层,我们把发起异步调用的方法挪到controller层,service只做数据库操作,servcie执行完事务提交完,再同步发起异步调用岂不就绕开了这个问题。

@RestController
public class TaskController {
    @Resource TaskService taskService;
    @PostMapping
    public String save() {
        taskService.saveExportTask();
        // 3. execute xxl-job
    }
}

如果秉持着代码和人有一个能跑就行的原则,此时已经结束战斗了,对于秉持着该原则且有点代码洁癖同学顶多也就是把触发任务的动作封装到一个触发service里调用。

TransactionSynchronizationManager事务回调

当然还是有很多同学对待技术是追求极致精神的,那么有没有优雅的方式去解决这个问题,那就要看springboot的事务回调能力了。

TransactionSynchronizationManager 事务同步器,从new TransactionSynchronization()可实现的方法上即可管中窥豹可见一斑,我们完全可以通过实现歇歇方法实现事务完成后回调的逻辑。

直接上代码举例子

@Service
public class TaskService {
    @Transactional(rollbackFor = Exception.class)
    public String saveExportTask() {
        // 1. save export task
        // 2. save export task history 
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter(){
            public void afterCommit(){
                System.out.println("commit!!!");
                // 3. execute xxl-job
            }
        });
    }
}

这样一来就可以保证是在事务结束之后去执行xxl-job的任务。

@TransactionalEventListener注解要和事务事件监控

TransactionalEventListener,自 Spring 4.2 以来,可以使用基于注释的配置为提交后事件(或更一般的事务同步事件,例如回滚)定义侦听器。本质上是基于核心 spring中的事件处理。使用这种方法可以避免对 TransactionSynchronizationManager 的硬编码。

首先需要自定义监听器

@Component
public class TaskEventListener {
   @Autowired
   private TaskService taskService;
   @TransactionalEventListener
   public void handleOrderCreatedEvent(TaskCreatedEvent event) {
      Task task = event.getTask();
      // 处理订单创建事件
      try {
         taskService.processOrder(task);
      } catch (Exception e) {
         // 处理失败,抛出异常,事务回滚
         throw new RuntimeException(e);
      }
   }
   @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
   public void handleOrderCompletedEvent(TaskCompletedEvent event) {
      Task task = event.getTask();
      // 处理订单完成事件
      try {
         taskService.sendOrderConfirmationEmail(task);
      } catch (Exception e) {
         // 处理失败,不影响事务
         e.printStackTrace();
      }
   }
}

定义事件

public class TaskCompletedEvent {
   private Task task;
   public TaskCompletedEvent(Task task) {
      this.task = task;
   }
   public Task getTask() {
      return task;
   }
}

@TransactionalEventListener注解要和@Transactional注解配合使用,确保在事务完成后才会触发回调方法。@TransactionalEventListener注解也可以指定回调方法的触发时机,可以选择在事务提交后触发(默认)或在事务回滚后触发。

到此这篇关于SpringBoot事务异步调用引发的bug解决的文章就介绍到这了,更多相关SpringBoot事务异步调内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • HashMap的get()方法的NullPointerException问题

    HashMap的get()方法的NullPointerException问题

    这篇文章主要介绍了HashMap的get()方法的NullPointerException问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-09-09
  • SpringMVC实现多文件上传

    SpringMVC实现多文件上传

    这篇文章主要为大家详细介绍了SpringMVC实现多文件上传功能,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-11-11
  • 关于Android触摸事件分发的原理详析

    关于Android触摸事件分发的原理详析

    触摸事件分发机制一直以来都是Android中比较重要的一大块,自定义view,各种复杂的自定义手势交互都与触摸事件分发机制关系密,下面这篇文章主要给大家介绍了关于Android触摸事件分发原理的相关资料,需要的朋友可以参考下
    2022-01-01
  • Future与FutureTask接口实现示例详解

    Future与FutureTask接口实现示例详解

    这篇文章主要为大家介绍了Future与FutureTask接口实现示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-10-10
  • Java设计模式编程之工厂方法模式的使用

    Java设计模式编程之工厂方法模式的使用

    这篇文章主要介绍了Java设计模式编程之工厂方法模式的使用,工厂方法模式属于设计模式中三种工厂模式中的一种,需要的朋友可以参考下
    2016-02-02
  • EasyExcel实现导入+各种数据校验功能

    EasyExcel实现导入+各种数据校验功能

    这篇文章主要介绍了EasyExcel实现导入+各种数据校验,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2023-05-05
  • 解决spring cloud服务启动之后回到命令行会自动挂掉问题

    解决spring cloud服务启动之后回到命令行会自动挂掉问题

    这篇文章主要介绍了解决spring cloud服务启动之后回到命令行会自动挂掉问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-09-09
  • 深入理解可视化JVM 故障处理工具

    深入理解可视化JVM 故障处理工具

    这篇文章主要介绍了深入理解可视化JVM 故障处理工具,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-10-10
  • Android Java判断密码强度方法实例(强度显示)

    Android Java判断密码强度方法实例(强度显示)

    在现代社会中,密码是确保我们个人信息和账户安全的重要手段之一,为了提高密码的安全性,我们可以使用正则表达式来判断密码的强度,这篇文章主要给大家介绍了关于Android Java判断密码强度(强度显示)的相关资料,需要的朋友可以参考下
    2024-03-03
  • Java并发之Semaphore工具类r的全面解析

    Java并发之Semaphore工具类r的全面解析

    Semaphore 是 java.util.concurrent中非常有用的并发编程工具类,它通常被用于限制对某个资源或资源池的并发访问数量,下面我们就来深入了解一下Semaphore的具体使用吧
    2024-02-02

最新评论