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事务异步调内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java反射如何有效的修改final属性值详解

    Java反射如何有效的修改final属性值详解

    最近在工作中遇到一个需求,要利用反射对修饰符为final的成员变量进行修改,所以这篇文章主要给大家介绍了关于Java反射如何有效的修改final属性值的相关资料,文中通过示例代码介绍的非常详细,对需要的朋友可以参考下。
    2017-08-08
  • Java命名规则详细总结

    Java命名规则详细总结

    Class名应是首字母大写的名词。命名时应该使其简洁而又具有描述性。异常类的命名,应以Exception结尾。Interface的命名规则与Class相同
    2013-10-10
  • Java模拟新浪微博登陆抓取数据

    Java模拟新浪微博登陆抓取数据

    本文主要介绍了Java模拟新浪微博登陆抓取数据的实现方法。具有很好的参考价值,下面跟着小编一起来看下吧
    2017-02-02
  • 深入理解Java对象复制

    深入理解Java对象复制

    使用任何已有的工具,都没有直接使用 get set 方式进行,对象转换的速度快,虽然get set 方式代码对一些比较麻烦,但是效率要高一些的,推荐使用 MapStruct 方式.,需要的朋友可以参考下
    2021-05-05
  • @RequestParam注解加与不加有什么区别

    @RequestParam注解加与不加有什么区别

    这篇文章主要介绍了@RequestParam注解加与不加有什么区别,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-11-11
  • 用Java实现全国天气预报的api接口调用示例

    用Java实现全国天气预报的api接口调用示例

    查询天气预报在APP中常用的一个常用功能,本文实例讲述了java调用中国天气网api获得天气预报信息的方法。分享给大家供大家参考。
    2016-10-10
  • 在Ubuntu系统下安装JDK和Tomcat的教程

    在Ubuntu系统下安装JDK和Tomcat的教程

    这篇文章主要介绍了在Ubuntu系统下安装JDK和Tomcat的教程,这样便是在Linux系统下搭建完整的Java和JSP开发环境,需要的朋友可以参考下
    2015-08-08
  • MyBatis-plus报错Property ‘sqlSessionFactory‘ or ‘sqlSessionTemplate‘ are required的解决方法

    MyBatis-plus报错Property ‘sqlSessionFactory‘ or 

    这篇文章主要给大家介绍了MyBatis-plus 报错 Property ‘sqlSessionFactory‘ or ‘sqlSessionTemplate‘ are required的两种解决方法,如果遇到相同问题的朋友可以参考借鉴一下
    2023-12-12
  • Mybatis中where标签与if标签结合使用详细说明

    Mybatis中where标签与if标签结合使用详细说明

    mybatis中if和where用于动态sql的条件拼接,在查询语句中如果缺失某个条件,通过if和where标签可以动态的改变查询条件,下面这篇文章主要给大家介绍了关于Mybatis中where标签与if标签结合使用的详细说明,需要的朋友可以参考下
    2023-03-03
  • Spring的注解简单介绍

    Spring的注解简单介绍

    这篇文章主要介绍了Spring的注解简单介绍,具有一定借鉴价值,需要的朋友可以参考下
    2017-12-12

最新评论