回滾宣告式交易

前一節概述了如何在應用程式中以宣告方式為類別(通常是服務層類別)指定交易設定的基本知識。本節介紹如何在 XML 配置中以簡單、宣告式的方式控制交易的回滾。如需使用 @Transactional 註解以宣告方式控制回滾語意的詳細資訊,請參閱 @Transactional 設定

向 Spring Framework 的交易基礎架構表明交易工作需要回滾的建議方式,是從目前在交易上下文中執行的程式碼中擲回 Exception。Spring Framework 的交易基礎架構程式碼會捕捉任何未處理的 Exception,因為它會沿著呼叫堆疊向上冒泡,並判斷是否將交易標記為回滾。

在其預設配置中,Spring Framework 的交易基礎架構程式碼僅在執行階段、未檢查例外的情況下將交易標記為回滾。也就是說,當擲回的例外是 RuntimeException 的實例或子類別時。(預設情況下,Error 實例也會導致回滾)。

預設配置也提供對 Vavr 的 Try 方法的支援,以便在其傳回 'Failure' 時觸發交易回滾。這可讓您使用 Try 處理函數式風格的錯誤,並在發生失敗時自動回滾交易。如需 Vavr 的 Try 的詳細資訊,請參閱 官方 Vavr 文件。以下是如何將 Vavr 的 Try 與交易方法搭配使用的範例

  • Java

@Transactional
public Try<String> myTransactionalMethod() {
	// If myDataAccessOperation throws an exception, it will be caught by the
	// Try instance created with Try.of() and wrapped inside the Failure class
	// which can be checked using the isFailure() method on the Try instance.
	return Try.of(delegate::myDataAccessOperation);
}

從 Spring Framework 6.1 開始,也對 CompletableFuture(和一般 Future)傳回值進行特殊處理,如果從原始方法傳回時異常完成,則會觸發此類句柄的回滾。這適用於 @Async 方法,其中實際方法實作可能需要符合 CompletableFuture 簽章(在執行階段由 @Async 處理自動調整為對 Proxy 的呼叫的實際非同步句柄),優先在傳回的句柄中公開,而不是重新擲回例外

  • Java

@Transactional @Async
public CompletableFuture<String> myTransactionalMethod() {
	try {
		return CompletableFuture.completedFuture(delegate.myDataAccessOperation());
	}
	catch (DataAccessException ex) {
		return CompletableFuture.failedFuture(ex);
	}
}

在預設配置中,從交易方法擲回的已檢查例外不會導致回滾。您可以透過指定回滾規則來配置確切的 Exception 類型,以將交易標記為回滾,包括已檢查例外。

回滾規則

回滾規則決定在擲回給定例外時是否應回滾交易,規則基於例外類型或例外模式。

回滾規則可以在 XML 中透過 rollback-forno-rollback-for 屬性配置,這些屬性允許將規則定義為模式。當使用 @Transactional 時,回滾規則可以透過 rollbackFor/noRollbackForrollbackForClassName/noRollbackForClassName 屬性配置,這些屬性允許根據例外類型或模式定義規則。

當使用例外類型定義回滾規則時,該類型將用於比對擲回的例外類型及其父類型,從而提供類型安全,並避免在使用模式時可能發生的任何意外比對。例如,jakarta.servlet.ServletException.class 的值將僅比對 jakarta.servlet.ServletException 及其子類型的擲回例外。

當使用例外模式定義回滾規則時,該模式可以是例外類型的完整類別名稱,也可以是例外類型完整類別名稱的子字串(必須是 Throwable 的子類別),目前不支援萬用字元。例如,"jakarta.servlet.ServletException""ServletException" 的值將比對 jakarta.servlet.ServletException 及其子類別。

您必須仔細考慮模式的具體程度,以及是否包含套件資訊(這不是強制性的)。例如,"Exception" 將比對幾乎任何內容,並且可能會隱藏其他規則。如果 "Exception" 旨在定義適用於所有已檢查例外的規則,則 "java.lang.Exception" 將是正確的。對於更獨特的例外名稱,例如 "BaseBusinessException",可能不需要使用例外模式的完整類別名稱。

此外,基於模式的回滾規則可能會導致與類似名稱的例外和巢狀類別發生意外比對。這是因為,如果擲回的例外的名稱包含為回滾規則配置的例外模式,則擲回的例外被視為與給定的基於模式的回滾規則相符。例如,給定配置為比對 "com.example.CustomException" 的規則,該規則將比對名為 com.example.CustomExceptionV2 的例外(與 CustomException 在同一套件中但具有額外字尾的例外)或名為 com.example.CustomException$AnotherException 的例外(宣告為 CustomException 中巢狀類別的例外)。

以下 XML 片段示範如何透過 rollback-for 屬性提供例外模式,為已檢查的應用程式特定 Exception 類型配置回滾

<tx:advice id="txAdvice" transaction-manager="txManager">
	<tx:attributes>
		<tx:method name="get*" read-only="true" rollback-for="NoProductInStockException"/>
		<tx:method name="*"/>
	</tx:attributes>
</tx:advice>

如果您不希望在擲回例外時回滾交易,您也可以指定 'no rollback' 規則。以下範例告知 Spring Framework 的交易基礎架構即使在面對未處理的 InstrumentNotFoundException 時也提交相關交易

<tx:advice id="txAdvice">
	<tx:attributes>
		<tx:method name="updateStock" no-rollback-for="InstrumentNotFoundException"/>
		<tx:method name="*"/>
	</tx:attributes>
</tx:advice>

當 Spring Framework 的交易基礎架構捕捉到例外並查閱配置的回滾規則以判斷是否將交易標記為回滾時,最強的比對規則獲勝。因此,在以下配置的情況下,除了 InstrumentNotFoundException 之外的任何例外都會導致相關交易回滾

<tx:advice id="txAdvice">
	<tx:attributes>
		<tx:method name="*" rollback-for="Throwable" no-rollback-for="InstrumentNotFoundException"/>
	</tx:attributes>
</tx:advice>

您也可以以程式設計方式指示所需的回滾。雖然很簡單,但此過程具有相當大的侵入性,並將您的程式碼與 Spring Framework 的交易基礎架構緊密耦合。以下範例顯示如何以程式設計方式指示所需的回滾

  • Java

  • Kotlin

public void resolvePosition() {
	try {
		// some business logic...
	} catch (NoProductInStockException ex) {
		// trigger rollback programmatically
		TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
	}
}
fun resolvePosition() {
	try {
		// some business logic...
	} catch (ex: NoProductInStockException) {
		// trigger rollback programmatically
		TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
	}
}

強烈建議您盡可能使用宣告式方法進行回滾。如果您絕對需要,可以使用程式化回滾,但其使用方式與實現乾淨的基於 POJO 的架構背道而馳。