Spring中使用@Async與@Transactional協調非同步與事務處理

banq發表於2024-02-21

本文旨在闡明 Spring@Transactional和@Async註釋的協同使用,提供對它們的集體應用程式的見解,以最佳化 Java 應用程式的效能。 

什麼是 Spring 中的事務管理
事務管理在任何企業應用程式中對於確保資料一致性和完整性都至關重要。在Spring中,這是透過@Transactional註解來實現的,註解抽象了底層的事務管理機制,使開發人員更容易以宣告方式控制事務邊界。

@Transactional 註解
Spring中的註解@Transactional可以應用於類和方法級別。它宣告類的一個方法或所有方法應該在事務上下文中執行。

Spring@Transactional支援各種屬性,例如propagation、isolation、timeout和readOnly,允許微調事務管理。

  • 傳播Propagation:定義事務如何相互關聯;常見選項包括REQUIRED、REQUIRES_NEW和SUPPORTS
  • 隔離性Isolation:確定一項事務所做的更改如何對其他事務可見;選項包括READ_UNCOMMITTED、READ_COMMITTED、REPEATABLE_READ和SERIALIZABLE。
  • 超時Timeout:指定事務必須完成的時間範圍
  • ReadOnly:表示事務是否只讀,最佳化某些資料庫操作

Spring中的非同步程式設計是什麼
Spring 中的非同步操作透過 @Async 註解進行管理,使方法呼叫在後臺執行緒池中執行,從而不會阻塞呼叫執行緒。這對於耗時或獨立於主執行流的操作尤其有利。

@Async 註解
只要 Spring 任務執行器配置正確,用 @Async 標記一個方法就能使其非同步執行。此註解可用於返回 void、Future 或 CompletableFuture 物件的方法,允許呼叫者跟蹤操作的進度和結果。

將 @Transactional 與 @Async 結合起來
將事務管理與非同步操作相結合會帶來獨特的挑戰和難度,這主要是因為事務與執行緒的上下文相關聯,而 @Async 會導致方法執行切換到不同的執行緒。

非同步與事務結合的問題
挑戰和考慮因素

  • 事務上下文傳播:從 @Transactional 上下文中呼叫 @Async 方法時,事務上下文不會自動傳播到非同步方法執行執行緒。
  • 最佳實踐:要在非同步方法中管理事務,關鍵是要確保負責事務管理的方法與標有 @Async 的方法不是同一個。相反,非同步方法應呼叫另一個 @Transactional 方法,以確保正確建立事務上下文。

案例程式碼

@Service
public class InvoiceService {

    @Async
    public void processInvoices() {
        <font>// Asynchronous operation<i>
        updateInvoiceStatus();
    }

    @Transactional
    public void updateInvoiceStatus() {
       
// Transactional operation<i>
    }
}


在本例中,processInvoices 是一個非同步方法,它呼叫 updateInvoiceStatus(一個事務方法)。這種分離可確保在非同步執行上下文中進行適當的事務管理。

@Service
public class ReportService {

    @Async
    public CompletableFuture<Report> generateReportAsync() {
        return CompletableFuture.completedFuture(generateReport());
    }

    @Transactional
    public Report generateReport() {
        <font>// Transactional operation to generate a report<i>
    }
}

在這裡,generateReportAsync 非同步執行並返回 CompletableFuture,而 generateReport 則處理報告生成的事務性問題。

關於事務傳播的討論
Spring 中的事務傳播行為定義了事務之間的關係,尤其是在一個事務方法呼叫另一個事務方法的情況下。選擇正確的傳播行為對於實現所需的事務語義至關重要。

  • REQUIRED (預設):這是預設的傳播行為。如果存在現有事務,方法將在該事務中執行。如果沒有現有事務,Spring 將建立一個新事務。
  • REQUIRES_NEW:此行為總是啟動一個新事務。如果有一個現有事務,它將被暫停,直到新事務完成。當你需要確保方法在一個新的、獨立的事務中執行時,這種方式非常有用。
  • SUPPORTS:使用此行為時,如果存在事務,方法將在現有事務中執行。但是,如果沒有現有事務,方法將以非事務方式執行。
  • NOT_SUPPORTED(不支援):此行為將以非事務方式執行方法。如果存在事務,該事務將被暫停,直到方法完成。
  • MANDATORY(必須):此行為要求有一個現有事務。如果沒有現有事務,Spring 將丟擲異常。
  • NEVER:該方法絕不應在事務中執行。如果存在事務,Spring 將丟擲異常。
  • NESTED:如果存在現有事務,該行為將啟動巢狀事務。巢狀事務允許部分提交和回滾,某些事務管理器(但並非所有事務管理器)都支援巢狀事務。

非同步操作的傳播行為
將 @Transactional 與 @Async 結合使用時,理解傳播行為的含義變得更加重要。由於非同步方法在單獨的執行緒中執行,某些傳播行為可能會因為新執行緒中沒有事務上下文而無法按預期執行。

  • REQUIRED 和 REQUIRES_NEW:這是最常用、最直接的行為。不過,當與 @Async 一起使用時,REQUIRES_NEW 行為通常更可預測,因為它能確保非同步方法始終啟動一個新事務,避免與呼叫方法的事務發生意外互動。
  • SUPPORTS、NOT_SUPPORTED、MANDATORY、NEVER:當與 @Async 一起使用時,這些行為可能會導致意想不到的結果,因為呼叫執行緒的事務上下文不會傳播到非同步方法的執行緒。在非同步處理中使用這些行為時,需要仔細考慮和測試。
  • NESTED:鑑於巢狀事務的複雜性和 @Async 方法的獨立執行緒上下文,一般不建議在非同步操作中使用巢狀事務。它可能導致複雜的事務管理情況,難以除錯和維護。

非同步操作的傳播
為了說明不同傳播行為與非同步操作之間的互動,讓我們來考慮一個非同步服務方法呼叫具有不同傳播行為的事務方法的示例。

@Service
public class OrderProcessingService {

    @Autowired
    private OrderUpdateService orderUpdateService;

    @Async
    public void processOrdersAsync(List<Order> orders) {
        orders.forEach(order -> orderUpdateService.updateOrderStatus(order, Status.PROCESSING));
    }
}

@Service
public class OrderUpdateService {

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void updateOrderStatus(Order order, Status status) {
        <font>// Implementation to update order status<i>
    }
}

在本例中,processOrdersAsync 是一個非同步方法,用於處理訂單列表。它呼叫每個訂單上的 updateOrderStatus,並用 @Transactional(propagation = Propagation.REQUIRES_NEW)標記。這確保了每個訂單狀態更新都發生在一個新的、獨立的事務中,將每個更新操作與其他操作和原始非同步流程隔離開來。

示例@Transactional(REQUIRES_NEW) 與@Async

@Service
public class UserService {

    @Async
    public void updateUserAsync(User user) {
        updateUser(user); <font>// Delegate to the synchronous, transactional method<i>
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void updateUser(User user) {
       
// 更新使用者的邏輯<i>
       
// 持續更改的資料庫操作<i>
    }
}

在這裡,updateUserAsync 是一個非同步方法,它呼叫 updateUser,這是一個註釋了 @Transactional 和 REQUIRES_NEW 傳播行為的方法。這種配置可確保每次使用者更新操作都在一個新事務中進行,並與任何現有事務隔離。這在更新操作必須不受其他事務結果影響的情況下特別有用。

在類級別上將 @Async 與 @Transactional 結合起來

@Service
@Transactional
public class OrderService {

    @Async
    public void processOrderAsync(Order order) {
        processOrder(order); <font>// 委託同步方法<i>
    }

    public void processOrder(Order order) {
       
// 處理訂單的邏輯<i>
       
// 訂單處理過程中涉及的資料庫操作<i>
    }
}

在這種情況下,OrderService 類被註釋為 @Transactional,預設情況下對其所有方法應用事務管理。帶有 @Async 標記的 processOrderAsync 方法透過呼叫 processOrder 非同步執行訂單處理。類級 @Transactional 註解確保訂單業務處理邏輯能在事務上下文中執行,從而為相關資料庫操作提供一致性和完整性。

@Async 方法呼叫多個 @Transactional 方法

@Service
public class ReportGenerationService {

    @Autowired
    private DataService dataService;

    @Async
    public void generateReportAsync(ReportParameters params) {
        Report report = dataService.prepareData(params);
        dataService.saveReport(report);
    }
}

@Service
public class DataService {

    @Transactional
    public Report prepareData(ReportParameters params) {
        <font>// 資料準備邏輯<i>
        return new Report();
//實際報告生成的佔位符<i>
    }

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void saveReport(Report report) {
       
//儲存報告的邏輯<i>
    }
}

此示例中的非同步方法 generateReportAsync 透過呼叫兩個獨立的事務方法:prepareData 和 saveReport 來協調報告生成過程。

  • prepareData 方法封裝在預設事務上下文中,
  • 而 saveReport 被明確配置為始終在新事務中執行。

這種設定非常適合報告儲存操作saveReport 需要在事務上獨立於資料準備階段prepareData 的情況,可確保報告的儲存saveReport不受前面操作成功或失敗的影響。

上述每個示例都展示瞭如何透過 @Transactional 和 @Async 的不同組合,在非同步處理上下文中實現特定的事務行為,從而為 Spring 開發人員提供了根據應用程式要求定製事務管理策略的靈活性。

結論
在 Spring 應用程式中,瞭解並謹慎選擇適當的事務傳播行為至關重要,尤其是在將事務操作與非同步處理相結合時。透過考慮每種傳播行為的具體要求和影響,開發人員可以在其 Spring 應用程式中設計出更穩健、高效和可靠的事務管理策略。有了這些擴充套件知識,就能更自信、更精確地處理複雜的事務場景,最終實現更高質量的軟體解決方案。

相關文章