SpringBoot事務異步調用引發(fā)的bug解決
前言
日常開發(fā)中有沒有遇到這種場景,save一條數(shù)據(jù)后發(fā)起一次異步調用,舉個例子,假設我們以mysql組件和xxl-job組件為例,創(chuàng)建一條數(shù)據(jù)導出任務,創(chuàng)建后默認啟動任務。那么邏輯可能大致為三步。
- 創(chuàng)建導出任務(DB.EXPORT_TASK)
- 創(chuàng)建導出任務歷史記錄(DB.EXPORT_TASK_HISTORY)
- 觸發(fā)導出任務(XXL-JOB)
因為需要同時要創(chuàng)建導出任務和導出任務歷史兩條記錄,所以代碼中需要通常要添加事務
@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去觸發(fā)任務是一個異步調用的過程,當xxl-job回調執(zhí)行器去執(zhí)行時可能需要根據(jù)job_id獲取到導出任務的配置,通過查詢db獲取任務詳情,比如導出地址了,導出規(guī)范等等。
看似非常和諧的場面,實際執(zhí)行起來則會出現(xiàn)任務不存在的問題。問題的根源其實也很好理解,就是因為在異步方法里做了同步的事就會出現(xiàn)這種問題,當?shù)谝徊經]有執(zhí)行完,第三步的回調方法已經到執(zhí)行器了,也就是說一個任務還沒存到數(shù)據(jù)庫,執(zhí)行這個任務時去數(shù)據(jù)庫查該任務的明細肯定會報任務不存在異常了。
那么如何解決呢。
代碼拆分
最簡單的一個方案,web應用通常劃分為controller、service、dao層那么幾層,業(yè)務邏輯按規(guī)范寫在service層,我們把發(fā)起異步調用的方法挪到controller層,service只做數(shù)據(jù)庫操作,servcie執(zhí)行完事務提交完,再同步發(fā)起異步調用豈不就繞開了這個問題。
@RestController
public class TaskController {
@Resource TaskService taskService;
@PostMapping
public String save() {
taskService.saveExportTask();
// 3. execute xxl-job
}
}如果秉持著代碼和人有一個能跑就行的原則,此時已經結束戰(zhàn)斗了,對于秉持著該原則且有點代碼潔癖同學頂多也就是把觸發(fā)任務的動作封裝到一個觸發(fā)service里調用。
TransactionSynchronizationManager事務回調
當然還是有很多同學對待技術是追求極致精神的,那么有沒有優(yōu)雅的方式去解決這個問題,那就要看springboot的事務回調能力了。
TransactionSynchronizationManager 事務同步器,從new TransactionSynchronization()可實現(xiàn)的方法上即可管中窺豹可見一斑,我們完全可以通過實現(xiàn)歇歇方法實現(xiàn)事務完成后回調的邏輯。

直接上代碼舉例子
@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
? ? ? ? ? ? }
? ? ? ? });
? ? }
}這樣一來就可以保證是在事務結束之后去執(zhí)行xxl-job的任務。
@TransactionalEventListener注解要和事務事件監(jiān)控
TransactionalEventListener,自 Spring 4.2 以來,可以使用基于注釋的配置為提交后事件(或更一般的事務同步事件,例如回滾)定義偵聽器。本質上是基于核心 spring中的事件處理。使用這種方法可以避免對 TransactionSynchronizationManager 的硬編碼。
首先需要自定義監(jiān)聽器
@Component
public class TaskEventListener {
? ?@Autowired
? ?private TaskService taskService;
? ?@TransactionalEventListener
? ?public void handleOrderCreatedEvent(TaskCreatedEvent event) {
? ? ? Task task = event.getTask();
? ? ? // 處理訂單創(chuàng)建事件
? ? ? 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注解配合使用,確保在事務完成后才會觸發(fā)回調方法。@TransactionalEventListener注解也可以指定回調方法的觸發(fā)時機,可以選擇在事務提交后觸發(fā)(默認)或在事務回滾后觸發(fā)。
到此這篇關于SpringBoot事務異步調用引發(fā)的bug解決的文章就介紹到這了,更多相關SpringBoot事務異步調內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
在Ubuntu系統(tǒng)下安裝JDK和Tomcat的教程
這篇文章主要介紹了在Ubuntu系統(tǒng)下安裝JDK和Tomcat的教程,這樣便是在Linux系統(tǒng)下搭建完整的Java和JSP開發(fā)環(huán)境,需要的朋友可以參考下2015-08-08
MyBatis-plus報錯Property ‘sqlSessionFactory‘ or 
這篇文章主要給大家介紹了MyBatis-plus 報錯 Property ‘sqlSessionFactory‘ or ‘sqlSessionTemplate‘ are required的兩種解決方法,如果遇到相同問題的朋友可以參考借鑒一下2023-12-12

