Spring Boot + 事務鉤子函數,打造高效支付系統!

来源:https://www.cnblogs.com/javastack/p/18174916
-Advertisement-
Play Games

作者:avengerEug 鏈接:https://juejin.cn/post/6984574787511123999 前言 經過前面對Spring AOP、事務的總結,我們已經對它們有了一個比較感性的認知了。 今天,我繼續安利一個獨門絕技:Spring 事務的鉤子函數。單純的講技術可能比較枯燥乏味 ...


作者:avengerEug
鏈接:https://juejin.cn/post/6984574787511123999

前言

經過前面對Spring AOP、事務的總結,我們已經對它們有了一個比較感性的認知了。

今天,我繼續安利一個獨門絕技:Spring 事務的鉤子函數。單純的講技術可能比較枯燥乏味。接下來,我將以一個實際的案例來描述Spring事務鉤子函數的正確使用姿勢。

一、案例背景

拿支付系統相關的業務來舉例。

在支付系統中,我們需要記錄每個賬戶的資金流水(記錄用戶A因為哪個操作扣了錢,因為哪個操作加了錢),這樣我們才能對每個賬戶的做到心中有數,對於支付系統而言,資金流水的數據可謂是最重要的。

因此,為了防止支付系統的老大徇私舞弊,CTO提了一個流水存檔的需求:要求支付系統對每個賬戶的資金流水做一份存檔,要求支付系統在寫流水的時候,把流水相關的信息以消息的形式推送到kafka,由存檔系統消費這個消息並落地到庫里(這個庫只有存檔系統擁有寫許可權)。

推薦一個開源免費的 Spring Boot 實戰項目:

https://github.com/javastacks/spring-boot-best-practice

整個需求的流程如下所示:

整個需求的流程還是比較簡單的,考慮到後續會有其他事業部也要進行數據存檔操作,CTO建議支付系統團隊內部開發一個二方庫,這個二方庫的主要功能就是發送消息到kafka中去。

二、確定方案

既然要求開發一個二方庫,因此,我們需要考慮如下幾件事情:

1、技術棧使用的springboot,因此,這裡最好以starter的方式提供

2、二方庫需要發送消息給kafka,最好是二方庫內部基於kafka生產者的api創建生產者,不要使用Spring自帶的kafkaTemplate,因為集成方有可能已經使用了kafkaTemplate。不能與集成方造成衝突。

3、減少對接方的集成難度、學習成本,最好是提供一個簡單實用的api,業務側能簡單上手。

4、發送消息這個操作需要支持事務,儘量不影響主業務

在上述的幾件事情中,最需要註意的應該就是第4點:發送消息這個操作需要支持事務,儘量不影響主業務

這是什麼意思呢?

首先,儘量不影響主業務,這個最簡單的方式就是使用非同步機制。

其次,需要支持事務是指:假設我們的api是在事務方法內部調用的,那麼我們需要保證事務提交後再執行這個api。那麼,我們的流水落地api應該要有這樣的功能:

內部可以判斷當前是否存在事務,如果存在事務,則需要等事務提交後再非同步發送消息給kafka。

如果不存在事務則直接非同步發送消息給kafka。而且這樣的判斷邏輯得放在二方庫內部才行。那現在擺在我們面前的問題就是:我要如何判斷當前是否存在事務,以及如何在事務提交後再觸發我們自定義的邏輯呢?

三、TransactionSynchronizationManager顯神威

這個類內部所有的變數、方法都是static修飾的,也就是說它其實是一個工具類。是一個事務同步器。下述是流水落地API的偽代碼,這段代碼就解決了我們上述提到的疑問:

private final ExecutorService executor = Executors.newSingleThreadExecutor();

public void sendLog() {
    // 判斷當前是否存在事務
    if (!TransactionSynchronizationManager.isSynchronizationActive()) {
        // 無事務,非同步發送消息給kafka

        executor.submit(() -> {
            // 發送消息給kafka
            try {
                // 發送消息給kafka
            } catch (Exception e) {
                // 記錄異常信息,發郵件或者進入待處理列表,讓開發人員感知異常
            }
        });
        return;
    }

    // 有事務,則添加一個事務同步器,並重寫afterCompletion方法(此方法在事務提交後會做回調)
    TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {

        @Override
        public void afterCompletion(int status) {
            if (status == TransactionSynchronization.STATUS_COMMITTED) {
                // 事務提交後,再非同步發送消息給kafka
                executor.submit(() -> {
                    try {
	                    // 發送消息給kafka
                    } catch (Exception e) {
    	                // 記錄異常信息,發郵件或者進入待處理列表,讓開發人員感知異常
                    }
                });
            }
        }

    });

}

代碼比較簡單,其主要是TransactionSynchronizationManager的使用。

推薦一個開源免費的 Spring Boot 實戰項目:

https://github.com/javastacks/spring-boot-best-practice

3.1、判斷是否存在事務?

TransactionSynchronizationManager.isSynchronizationActive() 方法顯神威

我們先看下這個方法的源碼:

// TransactionSynchronizationManager.java類內部的部分代碼

private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
			new NamedThreadLocal<>("Transaction synchronizations");

public static boolean isSynchronizationActive() {
    return (synchronizations.get() != null);
}

很明顯,synchronizations是一個線程變數(ThreadLocal)。那它是在什麼時候set進去的呢?

這裡的話,可以參考下這個方法:org.springframework.transaction.support.TransactionSynchronizationManager#initSynchronization,其源碼如下所示:

/**
  * Activate transaction synchronization for the current thread.
  * Called by a transaction manager on transaction begin.
  * @throws IllegalStateException if synchronization is already active
  */
public static void initSynchronization() throws IllegalStateException {
    if (isSynchronizationActive()) {
        throw new IllegalStateException("Cannot activate transaction synchronization - already active");
    }
    logger.trace("Initializing transaction synchronization");
    synchronizations.set(new LinkedHashSet<>());
}

由源碼中的註釋也可以知道,它是在事務管理器開啟事務時調用的。

換句話說,只要我們的程式執行到帶有事務特性的方法時,就會線上程變數中放入一個LinkedHashSet,用來標識當前存在事務。只要isSynchronizationActive返回true,則代表當前有事務。

因此,結合這兩個方法我們是指能解決我們最開始提出的疑問:要如何判斷當前是否存在事務

3.2、如何在事務提交後觸發自定義邏輯?

TransactionSynchronizationManager.registerSynchronization()方法顯神威

我們來看下這個方法的源代碼:

/**
  * Register a new transaction synchronization for the current thread.
  * Typically called by resource management code.
  * <p>Note that synchronizations can implement the
  * {@link org.springframework.core.Ordered} interface.
  * They will be executed in an order according to their order value (if any).
  * @param synchronization the synchronization object to register
  * @throws IllegalStateException if transaction synchronization is not active
  * @see org.springframework.core.Ordered
  */
public static void registerSynchronization(TransactionSynchronization synchronization)
    throws IllegalStateException {

    Assert.notNull(synchronization, "TransactionSynchronization must not be null");
    if (!isSynchronizationActive()) {
        throw new IllegalStateException("Transaction synchronization is not active");
    }
    synchronizations.get().add(synchronization);
}

這裡又使用到了synchronizations線程變數,我們在判斷是否存在事務時,就是判斷這個線程變數內部是否有值。那我們現在想在事務提交後觸發自定義邏輯和這個有什麼關係呢?

我們在上面構建流水落地api的偽代碼中有向synchronizations內部添加了一個TransactionSynchronizationAdapter,內部並重寫了afterCompletion方法,其代碼如下所示:

TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {

    @Override
    public void afterCompletion(int status) {
        if (status == TransactionSynchronization.STATUS_COMMITTED) {
            // 事務提交後,再非同步發送消息給kafka
            executor.submit(() -> {
                    try {
	                    // 發送消息給kafka
                    } catch (Exception e) {
    	                // 記錄異常信息,發郵件或者進入待處理列表,讓開發人員感知異常
                    }
            });
        }
    }

});

我們結合registerSynchronization的源碼來看,其實這段代碼主要就是向線程變數內部的LinkedHashSet添加了一個對象而已,但就是這麼一個操作,讓Spring在事務執行的過程中變得“有事情可做”。這是什麼意思呢?

是因為Spring在執行事務方法時,對於操作事務的每一個階段都有一個回調操作,比如:trigger系列的回調

invoke系列的回調

而我們現在的需求就是在事務提交後觸發自定義的函數,那就是在invokeAfterCommit和invokeAfterCompletion這兩個方法來選了。首先,這兩個方法都會拿到所有TransactionSynchronization的集合(其中會包括我們上述添加的TransactionSynchronizationAdapter)。

但是要註意一點:invokeAfterCommit只能拿到集合,invokeAfterCompletion除了集合還有一個int類型的參數,而這個int類型的參數其實是當前事務的一種狀態。也就是說,如果我們重寫了invokeAfterCompletion方法,我們除了能拿到集合外,還能拿到當前事務的狀態。

因此,此時我們可以根據這個狀態來做不同的事情,比如:可以在事務提交時做自定義處理,也可以在事務回滾時做自定義處理等等。

四、總結

上面有說到,我們判斷當前是否存在事務、添加鉤子函數都是依賴線程變數的。因此,我們在使用過程中,一定要避免切換線程。否則會出現不生效的情況。

更多文章推薦:

1.Spring Boot 3.x 教程,太全了!

2.2,000+ 道 Java面試題及答案整理(2024最新版)

3.免費獲取 IDEA 激活碼的 7 種方式(2024最新版)

覺得不錯,別忘了隨手點贊+轉發哦!


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • NumPy 創建數組 NumPy 中的核心數據結構是 ndarray,它代表多維數組。NumPy 提供了多種方法來創建 ndarray 對象,包括: 使用 array() 函數 array() 函數是最常用的方法之一,它可以將 Python 列表、元組甚至其他數組轉換為 ndarray 對象。 語法 ...
  • 刪除A中與B相同的元素 目錄刪除A中與B相同的元素程式驗證輸出結果 程式 #include <stdio.h> /******************************************************************* * * file name: 刪除A中與B相同的元 ...
  • OutOfMemoryError是Java程式中常見的異常,通常出現在記憶體不足時,導致程式無法運行。藉助MAT記憶體分析工具分析可能的記憶體泄漏代碼問題定位。 ...
  • 具有查看日期時間、天氣、工作日、記錄喝水、查詢微博熱搜、60s讀世界和各種小工具的桌面工具,可以在托盤區單擊隱藏和彈出,或使用快捷鍵Alt+1 ...
  • 變數、指針和關鍵字 兩個口訣: 變數變數,能變,就是能讀能寫,必定在記憶體(RAM)里 指針指針,保存的是地址,32 位處理器中的地址都是 32 位的,無論是什麼類型的指針變數,都是 4 位元組 指針 對於 32 位處理器裡面,地址是 32 位的,所以指針的大小為 4 位元組,sizeof(p) = 4 ...
  • MyBatis筆記 MyBatis介紹 MyBatis 是一個持久層框架 前身是ibatis, 在ibatis3.x 時,更名為MyBatis MyBatis 在java 和sql 之間提供更靈活的映射方案 mybatis 可以將對數據表的操作(sql,方法)等等直接剝離,寫到xml 配置文件,實現 ...
  • 在電子商務系統中,SKU(Stock Keeping Unit,庫存單位)和SPU(Standard Product Unit,標準產品單位)是兩種不同的概念,它們共同用於商品管理和庫存控制。雖然理論上可以只使用SKU來管理商品,但在實際應用中,同時使用SPU和SKU有其明顯的優勢和必要性。 SKU ...
  • 這一篇文章拖了有點久,雖然在項目中使用分散式鎖的頻率比較高,但整理成文章發佈出來還是花了一點時間。在一些移動端、用戶量大的互聯網項目中,經常會使用到 Redis 分散式鎖作為控制訪問高併發的工具。 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...