使用 @Transactional

除了基於 XML 的宣告式交易組態方法外,您還可以使用基於註解的方法。直接在 Java 原始碼中宣告交易語意,使宣告更接近受影響的程式碼。不會有過度耦合的風險,因為預期以交易方式使用的程式碼幾乎總是這樣部署。

標準的 jakarta.transaction.Transactional 註解也受支援,可作為 Spring 自身註解的直接替代品。請參閱 JTA 文件以取得更多詳細資訊。

使用 @Transactional 註解的易用性,最好用一個範例來說明,以下文字將對此進行解釋。請考慮以下類別定義

  • Java

  • Kotlin

// the service class that we want to make transactional
@Transactional
public class DefaultFooService implements FooService {

	@Override
	public Foo getFoo(String fooName) {
		// ...
	}

	@Override
	public Foo getFoo(String fooName, String barName) {
		// ...
	}

	@Override
	public void insertFoo(Foo foo) {
		// ...
	}

	@Override
	public void updateFoo(Foo foo) {
		// ...
	}
}
// the service class that we want to make transactional
@Transactional
class DefaultFooService : FooService {

	override fun getFoo(fooName: String): Foo {
		// ...
	}

	override fun getFoo(fooName: String, barName: String): Foo {
		// ...
	}

	override fun insertFoo(foo: Foo) {
		// ...
	}

	override fun updateFoo(foo: Foo) {
		// ...
	}
}

如上所示在類別層級使用,此註解指示宣告類別(及其子類別)的所有方法的預設值。或者,可以單獨註解每個方法。請參閱方法可見性,以進一步瞭解 Spring 認為哪些方法是交易性的。請注意,類別層級註解不適用於類別階層結構中的祖先類別;在這種情況下,需要在本機重新宣告繼承的方法,才能參與子類別層級註解。

當如上所示的 POJO 類別在 Spring Context 中定義為 Bean 時,您可以透過 @Configuration 類別中的 @EnableTransactionManagement 註解,使 Bean 實例成為交易性的。請參閱 javadoc 以取得完整詳細資訊。

在 XML 組態中,<tx:annotation-driven/> 標籤提供了類似的便利性

<!-- from the file 'context.xml' -->
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns:aop="http://www.springframework.org/schema/aop"
	xmlns:tx="http://www.springframework.org/schema/tx"
	xsi:schemaLocation="
		http://www.springframework.org/schema/beans
		https://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/tx
		https://www.springframework.org/schema/tx/spring-tx.xsd
		http://www.springframework.org/schema/aop
		https://www.springframework.org/schema/aop/spring-aop.xsd">

	<!-- this is the service object that we want to make transactional -->
	<bean id="fooService" class="x.y.service.DefaultFooService"/>

	<!-- enable the configuration of transactional behavior based on annotations -->
	<!-- a TransactionManager is still required -->
	<tx:annotation-driven transaction-manager="txManager"/> (1)

	<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<!-- (this dependency is defined somewhere else) -->
		<property name="dataSource" ref="dataSource"/>
	</bean>

	<!-- other <bean/> definitions here -->

</beans>
1 使 Bean 實例成為交易性的程式碼行。
如果想要連線的 TransactionManager 的 Bean 名稱是 transactionManager,則可以省略 <tx:annotation-driven/> 標籤中的 transaction-manager 屬性。如果想要相依性注入的 TransactionManager Bean 有任何其他名稱,則必須使用 transaction-manager 屬性,如前面的範例所示。

反應式交易方法使用反應式傳回類型,與命令式程式設計安排形成對比,如下面的清單所示

  • Java

  • Kotlin

// the reactive service class that we want to make transactional
@Transactional
public class DefaultFooService implements FooService {

	@Override
	public Publisher<Foo> getFoo(String fooName) {
		// ...
	}

	@Override
	public Mono<Foo> getFoo(String fooName, String barName) {
		// ...
	}

	@Override
	public Mono<Void> insertFoo(Foo foo) {
		// ...
	}

	@Override
	public Mono<Void> updateFoo(Foo foo) {
		// ...
	}
}
// the reactive service class that we want to make transactional
@Transactional
class DefaultFooService : FooService {

	override fun getFoo(fooName: String): Flow<Foo> {
		// ...
	}

	override fun getFoo(fooName: String, barName: String): Mono<Foo> {
		// ...
	}

	override fun insertFoo(foo: Foo): Mono<Void> {
		// ...
	}

	override fun updateFoo(foo: Foo): Mono<Void> {
		// ...
	}
}

請注意,對於傳回的 Publisher,關於 Reactive Streams 取消訊號有一些特殊考量。如需更多詳細資訊,請參閱「使用 TransactionalOperator」下的取消訊號區段。

Proxy 模式中的方法可見性和 @Transactional

@Transactional 註解通常用於具有 public 可見性的方法。從 6.0 開始,預設情況下,protected 或套件可見的方法也可以透過基於類別的 Proxy 設為交易性。請注意,基於介面的 Proxy 中的交易方法必須始終是 public,並且在 Proxy 的介面中定義。對於這兩種 Proxy,只有透過 Proxy 傳入的外部方法呼叫會被攔截。

如果您希望在不同種類的 Proxy 中一致地處理方法可見性(這是 5.3 之前的預設值),請考慮指定 publicMethodsOnly

/**
 * Register a custom AnnotationTransactionAttributeSource with the
 * publicMethodsOnly flag set to true to consistently ignore non-public methods.
 * @see ProxyTransactionManagementConfiguration#transactionAttributeSource()
 */
@Bean
TransactionAttributeSource transactionAttributeSource() {
	return new AnnotationTransactionAttributeSource(true);
}

Spring TestContext Framework 也預設支援非私有的 @Transactional 測試方法。如需範例,請參閱測試章節中的交易管理

您可以將 @Transactional 註解套用至介面定義、介面上的一個方法、類別定義或類別上的一個方法。但是,僅僅存在 @Transactional 註解不足以啟動交易行為。@Transactional 註解僅僅是元資料,可由相應的執行階段基礎架構使用,該基礎架構使用該元資料來組態具有交易行為的適當 Bean。在前面的範例中,<tx:annotation-driven/> 元素開啟了執行階段的實際交易管理。

Spring 團隊建議您使用 @Transactional 註解來註解具體類別的方法,而不是依賴介面中的註解方法,即使後者對於基於介面的 Proxy 和目標類別 Proxy 在 5.0 及更高版本中有效。由於 Java 註解不會從介面繼承,因此在使用 AspectJ 模式時,編織基礎架構仍然無法辨識介面宣告的註解,因此不會套用切面。因此,您的交易註解可能會被靜默忽略:您的程式碼在您測試回滾情境之前可能會看起來「正常運作」。
在 Proxy 模式(預設模式)中,只有透過 Proxy 傳入的外部方法呼叫會被攔截。這表示自我調用(實際上,目標物件內的方法呼叫目標物件的另一個方法)即使調用的方法標記有 @Transactional,也不會在執行階段導致實際交易。此外,Proxy 必須完全初始化才能提供預期的行為,因此您不應在初始化程式碼(例如,在 @PostConstruct 方法中)中依賴此功能。

如果您希望自我調用也以交易方式包裝,請考慮使用 AspectJ 模式(請參閱下表中的 mode 屬性)。在這種情況下,首先沒有 Proxy。相反,目標類別會被編織(也就是說,修改其位元組碼)以支援任何種類方法上的 @Transactional 執行階段行為。

表 1. 註解驅動的交易設定
XML 屬性 註解屬性 預設值 描述

transaction-manager

N/A(請參閱 TransactionManagementConfigurer javadoc)

transactionManager

要使用的交易管理器的名稱。只有在交易管理器的名稱不是 transactionManager 時才需要,如前面的範例所示。

mode

mode

proxy

預設模式 (proxy) 處理要使用 Spring 的 AOP 框架代理的註解 Bean(遵循 Proxy 語意,如前所述,僅適用於透過 Proxy 傳入的方法呼叫)。替代模式 (aspectj) 反而使用 Spring 的 AspectJ 交易切面編織受影響的類別,修改目標類別位元組碼以套用至任何種類的方法呼叫。AspectJ 編織需要在類別路徑中包含 spring-aspects.jar,並啟用載入時期編織(或編譯時期編織)。(如需如何設定載入時期編織的詳細資訊,請參閱 Spring 組態。)

proxy-target-class

proxyTargetClass

false

僅適用於 proxy 模式。控制為使用 @Transactional 註解的類別建立何種類型的交易 Proxy。如果 proxy-target-class 屬性設定為 true,則會建立基於類別的 Proxy。如果 proxy-target-classfalse 或省略此屬性,則會建立標準 JDK 基於介面的 Proxy。(如需不同 Proxy 類型的詳細檢查,請參閱Proxy 機制。)

order

order

Ordered.LOWEST_PRECEDENCE

定義套用至使用 @Transactional 註解的 Bean 的交易建議的順序。(如需有關 AOP 建議排序規則的更多資訊,請參閱建議排序。)未指定的排序表示 AOP 子系統決定建議的順序。

處理 @Transactional 註解的預設建議模式是 proxy,它僅允許攔截透過 Proxy 的呼叫。無法以這種方式攔截同一類別內的本機呼叫。如需更進階的攔截模式,請考慮切換到 aspectj 模式,並結合編譯時期或載入時期編織。
proxy-target-class 屬性控制為使用 @Transactional 註解的類別建立何種類型的交易 Proxy。如果 proxy-target-class 設定為 true,則會建立基於類別的 Proxy。如果 proxy-target-classfalse 或省略此屬性,則會建立標準 JDK 基於介面的 Proxy。(如需不同 Proxy 類型的討論,請參閱Proxy 機制。)
@EnableTransactionManagement<tx:annotation-driven/> 僅在定義它們的相同應用程式 Context 中尋找 @Transactional Bean。這表示,如果您在 DispatcherServletWebApplicationContext 中放置註解驅動的組態,它只會在您的控制器中檢查 @Transactional Bean,而不會在您的服務中檢查。如需更多資訊,請參閱MVC

在評估方法的交易設定時,最衍生的位置優先。在以下範例的情況下,DefaultFooService 類別在類別層級使用唯讀交易設定進行註解,但同一個類別中 updateFoo(Foo) 方法上的 @Transactional 註解優先於在類別層級定義的交易設定。

  • Java

  • Kotlin

@Transactional(readOnly = true)
public class DefaultFooService implements FooService {

	public Foo getFoo(String fooName) {
		// ...
	}

	// these settings have precedence for this method
	@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
	public void updateFoo(Foo foo) {
		// ...
	}
}
@Transactional(readOnly = true)
class DefaultFooService : FooService {

	override fun getFoo(fooName: String): Foo {
		// ...
	}

	// these settings have precedence for this method
	@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
	override fun updateFoo(foo: Foo) {
		// ...
	}
}

@Transactional 設定

@Transactional 註解是元資料,用於指定介面、類別或方法必須具有交易語意(例如,「當調用此方法時啟動一個全新的唯讀交易,暫停任何現有交易」)。預設的 @Transactional 設定如下

  • 傳播設定為 PROPAGATION_REQUIRED

  • 隔離等級為 ISOLATION_DEFAULT

  • 交易是讀寫的。

  • 交易逾時預設為基礎交易系統的預設逾時,如果交易系統不支援逾時,則為無逾時。

  • 任何 RuntimeExceptionError 都會觸發回滾,而任何已檢查的 Exception 則不會。

您可以變更這些預設設定。下表總結了 @Transactional 註解的各種屬性

表 2. @Transactional 設定
屬性 類型 描述

value

String

可選的限定詞,用於指定要使用的交易管理器。

transactionManager

String

value 的別名。

label

String 標籤陣列,用於為交易新增表達性描述。

交易管理器可能會評估標籤,以將實作特定的行為與實際交易關聯起來。

propagation

enumPropagation

可選的傳播設定。

isolation

enumIsolation

可選的隔離等級。僅適用於 REQUIREDREQUIRES_NEW 的傳播值。

timeout

int(以秒為單位)

可選的交易逾時。僅適用於 REQUIREDREQUIRES_NEW 的傳播值。

timeoutString

String(以秒為單位)

String 值(例如,作為預留位置)指定秒為單位的 timeout 的替代方案。

readOnly

boolean

讀寫與唯讀交易。僅適用於 REQUIREDREQUIRES_NEW 的值。

rollbackFor

Class 物件陣列,必須衍生自 Throwable

必須導致回滾的可選例外類型陣列。

rollbackForClassName

例外名稱模式陣列。

必須導致回滾的可選例外名稱模式陣列。

noRollbackFor

Class 物件陣列,必須衍生自 Throwable

可選的例外類型陣列,不得導致回滾。

noRollbackForClassName

例外名稱模式陣列。

可選的例外名稱模式陣列,不得導致回滾。

如需有關回滾規則語意、模式以及關於基於模式的回滾規則可能發生的意外比對的警告的更多詳細資訊,請參閱回滾規則

截至 6.2 版本,您可以全域變更預設的回滾行為 – 例如,透過 @EnableTransactionManagement(rollbackOn=ALL_EXCEPTIONS),導致交易中引發的所有例外狀況都回滾,包括任何受檢例外。如需進一步的自訂,AnnotationTransactionAttributeSource 提供了 addDefaultRollbackRule(RollbackRuleAttribute) 方法以用於自訂預設規則。

請注意,交易特定的回滾規則會覆寫預設行為,但會為未指定的例外狀況保留所選的預設行為。Spring 的 @Transactional 以及 JTA 的 jakarta.transaction.Transactional 注解都是如此。

除非您依賴具有提交行為的 EJB 樣式業務例外,否則建議切換到 ALL_EXCEPTIONS,以便即使在發生(可能意外的)受檢例外狀況時,也能保持一致的回滾語意。此外,建議針對完全不強制執行受檢例外狀況的 Kotlin 應用程式進行此切換。

目前,您無法明確控制交易的名稱,此處的「名稱」指的是交易監視器和記錄輸出中顯示的交易名稱。對於宣告式交易,交易名稱始終是事務性建議類別的完整類別名稱 + . + 方法名稱。例如,如果 BusinessService 類別的 handlePayment(..) 方法啟動了一個交易,則該交易的名稱將為 com.example.BusinessService.handlePayment

搭配 @Transactional 的多個交易管理器

大多數 Spring 應用程式只需要單一交易管理器,但在某些情況下,您可能希望在單一應用程式中使用多個獨立的交易管理器。您可以使用 @Transactional 注解的 valuetransactionManager 屬性,選擇性地指定要使用的 TransactionManager 的身分。這可以是 bean 名稱或交易管理器 bean 的限定詞值。例如,使用限定詞表示法,您可以將以下 Java 程式碼與應用程式內容中的以下交易管理器 bean 宣告結合使用

  • Java

  • Kotlin

public class TransactionalService {

	@Transactional("order")
	public void setSomething(String name) { ... }

	@Transactional("account")
	public void doSomething() { ... }

	@Transactional("reactive-account")
	public Mono<Void> doSomethingReactive() { ... }
}
class TransactionalService {

	@Transactional("order")
	fun setSomething(name: String) {
		// ...
	}

	@Transactional("account")
	fun doSomething() {
		// ...
	}

	@Transactional("reactive-account")
	fun doSomethingReactive(): Mono<Void> {
		// ...
	}
}

以下清單顯示了 bean 宣告

<tx:annotation-driven/>

	<bean id="transactionManager1" class="org.springframework.jdbc.support.JdbcTransactionManager">
		...
		<qualifier value="order"/>
	</bean>

	<bean id="transactionManager2" class="org.springframework.jdbc.support.JdbcTransactionManager">
		...
		<qualifier value="account"/>
	</bean>

	<bean id="transactionManager3" class="org.springframework.data.r2dbc.connection.R2dbcTransactionManager">
		...
		<qualifier value="reactive-account"/>
	</bean>

在這種情況下,TransactionalService 上的個別方法在不同的交易管理器下執行,這些交易管理器透過 orderaccountreactive-account 限定詞來區分。如果找不到明確限定的 TransactionManager bean,則仍然使用預設的 <tx:annotation-driven> 目標 bean 名稱 transactionManager

如果同一類別上的所有事務性方法都共用相同的限定詞,請考慮宣告類型層級的 org.springframework.beans.factory.annotation.Qualifier 注解。如果其值與特定交易管理器的限定詞值(或 bean 名稱)匹配,則該交易管理器將用於沒有在 @Transactional 本身上指定限定詞的交易定義。

這樣的類型層級限定詞可以在具體類別上宣告,也適用於來自基底類別的交易定義。這有效地覆寫了任何未限定的基底類別方法的預設交易管理器選擇。

最後但並非最不重要的是,這樣的類型層級 bean 限定詞可以有多種用途,例如,值為 "order" 時,它可以同時用於自動裝配目的(識別 order repository)和交易管理器選擇,只要自動裝配的目標 bean 以及相關的交易管理器定義宣告相同的限定詞值即可。這樣的限定詞值只需要在類型匹配的 bean 集合中是唯一的,而不需要作為 ID。

自訂組合注解

如果您發現您在許多不同的方法上重複使用 @Transactional 的相同屬性,Spring 的 meta-annotation 支援 讓您可以為您的特定用例定義自訂的組合注解。例如,考慮以下注解定義

  • Java

  • Kotlin

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional(transactionManager = "order", label = "causal-consistency")
public @interface OrderTx {
}

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional(transactionManager = "account", label = "retryable")
public @interface AccountTx {
}
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@Transactional(transactionManager = "order", label = ["causal-consistency"])
annotation class OrderTx

@Target(AnnotationTarget.FUNCTION, AnnotationTarget.TYPE)
@Retention(AnnotationRetention.RUNTIME)
@Transactional(transactionManager = "account", label = ["retryable"])
annotation class AccountTx

前面的注解讓我們可以將前一節的範例寫成如下

  • Java

  • Kotlin

public class TransactionalService {

	@OrderTx
	public void setSomething(String name) {
		// ...
	}

	@AccountTx
	public void doSomething() {
		// ...
	}
}
class TransactionalService {

	@OrderTx
	fun setSomething(name: String) {
		// ...
	}

	@AccountTx
	fun doSomething() {
		// ...
	}
}

在前面的範例中,我們使用了語法來定義交易管理器限定詞和事務性標籤,但我們也可以包含傳播行為、回滾規則、逾時和其他功能。