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

直接上代碼舉例子
@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
? ? ? ? ? ? }
? ? ? ? });
? ? }
}這樣一來就可以保證是在事務(wù)結(jié)束之后去執(zhí)行xxl-job的任務(wù)。
@TransactionalEventListener注解要和事務(wù)事件監(jiān)控
TransactionalEventListener,自 Spring 4.2 以來,可以使用基于注釋的配置為提交后事件(或更一般的事務(wù)同步事件,例如回滾)定義偵聽器。本質(zhì)上是基于核心 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) {
? ? ? ? ?// 處理失敗,拋出異常,事務(wù)回滾
? ? ? ? ?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) {
? ? ? ? ?// 處理失敗,不影響事務(wù)
? ? ? ? ?e.printStackTrace();
? ? ? }
? ?}
}定義事件
public class TaskCompletedEvent {
? ?private Task task;
? ?public TaskCompletedEvent(Task task) {
? ? ? this.task = task;
? ?}
? ?public Task getTask() {
? ? ? return task;
? ?}
}@TransactionalEventListener注解要和@Transactional注解配合使用,確保在事務(wù)完成后才會觸發(fā)回調(diào)方法。@TransactionalEventListener注解也可以指定回調(diào)方法的觸發(fā)時機(jī),可以選擇在事務(wù)提交后觸發(fā)(默認(rèn))或在事務(wù)回滾后觸發(fā)。
到此這篇關(guān)于SpringBoot事務(wù)異步調(diào)用引發(fā)的bug解決的文章就介紹到這了,更多相關(guān)SpringBoot事務(wù)異步調(diào)內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
日志模塊自定義@SkipLogAspect注解跳過切面的操作方法
文章介紹了一個自定義注解@SkipLogAspect,用于在日志模塊中跳過特定方法的日志切面,這個注解可以用于需要避免大對象轉(zhuǎn)換為JSON時導(dǎo)致的OOM問題,文章還提供了注解的實(shí)現(xiàn)代碼以及一個測試示例,展示了如何在控制器中使用該注解來跳過日志切面,感興趣的朋友一起看看吧2025-02-02
JDK12的新特性之CompactNumberFormat詳解
這篇文章主要介紹了JDK12的新特性之CompactNumberFormat,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-05-05
詳談Enumeration接口和Iterator接口的區(qū)別
下面小編就為大家?guī)硪黄斦凟numeration接口和Iterator接口的區(qū)別。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-08-08
詳解Java編程中統(tǒng)一資源定位符URL的相關(guān)使用
這篇文章主要介紹了Java編程中統(tǒng)一資源定位符URL的相關(guān)使用,是Java網(wǎng)絡(luò)編程中的基礎(chǔ)知識,需要的朋友可以參考下2015-10-10
Java畢業(yè)設(shè)計實(shí)戰(zhàn)之醫(yī)院心理咨詢問診系統(tǒng)的實(shí)現(xiàn)
這是一個使用了java+Spring+Maven+mybatis+Vue+mysql開發(fā)的醫(yī)院心理咨詢問診系統(tǒng),是一個畢業(yè)設(shè)計的實(shí)戰(zhàn)練習(xí),具有心理咨詢問診該有的所有功能,感興趣的朋友快來看看吧2022-01-01
MyBatis使用annonation定義類型映射的簡易用法示例
這篇文章主要介紹了MyBatis使用annonation定義類型映射的簡易用法示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09

