首頁  >  文章  >  Java  >  在 Spring 中使用事務處理非同步執行:一個常見的陷阱以及如何解決它

在 Spring 中使用事務處理非同步執行:一個常見的陷阱以及如何解決它

Barbara Streisand
Barbara Streisand原創
2024-11-09 06:42:02755瀏覽

Handling Asynchronous Execution with Transactions in Spring: A Common Pitfall and How to Solve It

在現代 Spring 應用程式中,將非同步執行與事務行為結合是很常見的。但是,使用 @Async@Transactional(propagation = Propagation.REQUIRES_NEW) 註解方法可能會導致意外行為,因為 Spring 管理非同步任務和交易。

在本文中,我們將詳細探討該問題並示範正確處理非同步執行和事務管理的解決方案。

問題:@Async 和 @Transactional(propagation = Propagation.REQUIRES_NEW)

考慮以下程式碼片段:

@Async
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveSomething() {
    // save-point one
    // save-point two
}

乍一看,似乎一切都如預期進行。但是,此配置存在一些關鍵問題,可能會導致意外行為。

幕後發生了什麼事?

  • @Async 註:

@Async 註解告訴 Spring 在單獨的執行緒中非同步執行該方法。這意味著該方法不會在呼叫它的原始執行緒中運行,而是會被卸載到執行緒池中的另一個執行緒。
Spring 使用代理來管理非同步方法。當您呼叫使用 @Async 註解的方法時,Spring 將執行委託給在不同執行緒中執行該方法的內部 Executor。

  • @Transactional(propagation = Propagation.REQUIRES_NEW) 註:

@Transactional(propagation = Propagation.REQUIRES_NEW) 註解可確保為該方法啟動一個新事務,無論任何現有事務如何。它掛起呼叫執行緒中的任何活動事務並為該方法開始一個新事務。

Spring 中的事務管理通常是執行緒綁定的,這表示事務上下文與當前執行緒綁定。

衝突

出現這個問題是因為@Async在不同的執行緒中執行該方法,而Spring的事務管理依賴執行緒來綁定事務。當該方法非同步執行時,來自呼叫線程的事務上下文不會傳播到新線程,這會導致以下問題:

  • @Transactional 註解不會在非同步執行緒中建立新事務,任何事務行為(如回滾、提交等)都不會被正確處理。
  • REQUIRES_NEW 傳播設定將不適用,因為非同步方法在原始事務上下文之外運作。

解決方案:解耦異步執行和事務

為了解決這個問題,您可以透過在單獨的服務方法中處理事務來將非同步執行與事務邏輯解耦。具體方法如下:

  • 第 1 步:為事務邏輯建立新的同步服務
    建立一個處理事務邏輯的新服務。此方法將同步執行(不使用@Async)以確保事務管理能如預期運作。

  • 第2步:非同步呼叫同步方法
    然後,您可以使用 @Async 非同步呼叫同步事務方法。這確保了在主執行緒中正確處理事務邏輯,並且仍然保持異步行為。

重構後的程式碼如下圖所示:

@Async
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveSomething() {
    // save-point one
    // save-point two
}

它是如何運作的?

重構後的解決方案中,透過使用 @Async 註解 saveSomethingAsync() 方法來實現非同步執行。這意味著當呼叫 saveSomethingAsync() 時,它將在由 Spring 的非同步任務執行器管理的單獨執行緒中執行。在不同的執行緒中運行它允許主執行緒繼續執行,而無需等待 saveSomethingAsync() 完成。這種方法對於您想要卸載長時間運行的任務、提高回應能力或同時處理獨立操作的場景很有用。

對於事務行為,TransactionalService 中的 saveSomething() 方法以 @Transactional(propagation = Propagation.REQUIRES_NEW) 進行註解。這確保每次呼叫 saveSomething() 都會建立一個獨立於呼叫方法中任何現有事務的新事務。 REQUIRES_NEW 傳播啟動一項新事務並暫停任何現有事務,從而允許 saveSomething() 在隔離的事務上下文中進行操作。這意味著即使原始呼叫方法有事務,saveSomething() 也將在其自己的單獨事務中工作,從而僅針對此操作啟用受控提交和回滾。

透過將非同步執行與事務邏輯解耦,我們確保事務管理能如預期運作。在此設定中,事務上下文在 saveSomething() 方法中保持正確處理,而 saveSomethingAsync() 方法繼續在單獨的執行緒中執行。這種關注點分離可以兼具非同步處理和可靠事務管理的優點,即使在並發處理時也能實現獨立且安全的資料操作。

何時使用此方法?

  • 當交易隔離至關重要時:如果您需要確保某些操作在單獨的事務中執行(即 REQUIRES_NEW),則此方法效果很好。

  • 非同步操作:如果您有長時間運行的獨立任務,需要非同步執行,但也需要自己的交易邊界。

替代方案:使用訊息佇列實作完全解耦

如果您需要更進階的解耦或希望處理重試、錯誤處理和長時間運行的流程,請考慮將任務卸載到 Kafka 或 RabbitMQ 等訊息佇列。透過使用訊息佇列,可以確保每個任務在自己的上下文中運行,並且可以獨立管理交易。

以上是在 Spring 中使用事務處理非同步執行:一個常見的陷阱以及如何解決它的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn