使用 @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 上下文中被定義為 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”下的取消訊號部分。

代理模式下的方法可見性和 `@Transactional`

@Transactional 註解通常用於 public 可見性的方法。自 6.0 版本起,對於基於類的代理,protected 或包可見的方法也可以預設設定為事務性。請注意,基於介面的代理中的事務方法必須始終是 public 的,並且在被代理的介面中定義。對於這兩種型別的代理,只有透過代理進入的外部方法呼叫才會被攔截。

如果您希望在不同型別的代理之間對方法可見性進行一致處理(這在 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 框架也預設支援非私有的 `@Transactional` 測試方法。有關示例,請參閱測試章節中的事務管理

您可以將 `@Transactional` 註解應用於介面定義、介面上的方法、類定義或類上的方法。然而,僅存在 `@Transactional` 註解不足以啟用事務行為。`@Transactional` 註解只是一種元資料,可以由相應的執行時基礎設施消費,該基礎設施利用這些元資料來配置具有事務行為的適當 Bean。在前面的示例中,`<tx:annotation-driven/>` 元素在執行時開啟了實際的事務管理。

Spring 團隊建議您使用 `@Transactional` 註解來註解具體類的方法,而不是依賴於介面中註解的方法,儘管後者在 5.0 版本後對基於介面和基於目標類的代理都有效。由於 Java 註解不會從介面繼承,因此在使用 AspectJ 模式時,編織基礎設施仍然不識別介面中宣告的註解,所以切面不會被應用。因此,您的事務註解可能會被靜默忽略:您的程式碼可能看起來“正常工作”,直到您測試回滾場景。
在代理模式下(這是預設模式),只有透過代理進入的外部方法呼叫才會被攔截。這意味著自呼叫(實際上是目標物件中的一個方法呼叫目標物件的另一個方法)在執行時不會導致實際的事務,即使被呼叫的方法標記了 `@Transactional`。此外,代理必須完全初始化才能提供預期的行為,因此您不應在初始化程式碼中依賴此特性,例如在 `@PostConstruct` 方法中。

如果您希望自呼叫也被事務包裝,請考慮使用 AspectJ 模式(參見下表中的 `mode` 屬性)。在這種情況下,首先沒有代理。相反,目標類會被編織(即其位元組碼被修改),以支援任何型別的方法上的 `@Transactional` 執行時行為。

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

transaction-manager

不適用 (參見 TransactionManagementConfigurer javadoc)

transactionManager

要使用的事務管理器的名稱。僅當事務管理器的名稱不是 transactionManager 時才需要,如前面的示例所示。

mode

mode

proxy

預設模式(`proxy`)使用 Spring 的 AOP 框架處理要透過代理進行代理的帶註解 Bean(遵循代理語義,如前所述,僅應用於透過代理進入的方法呼叫)。替代模式(`aspectj`)則使用 Spring 的 AspectJ 事務切面編織受影響的類,修改目標類的位元組碼以應用於任何型別的方法呼叫。AspectJ 編織需要在類路徑中包含 spring-aspects.jar,並且需要啟用載入時編織(或編譯時編織)。(有關如何設定載入時編織的詳細資訊,請參見Spring 配置。)

proxy-target-class

proxyTargetClass

false

僅適用於 `proxy` 模式。控制為帶有 `@Transactional` 註解的類建立哪種型別的事務代理。如果將 `proxy-target-class` 屬性設定為 `true`,則建立基於類的代理。如果 `proxy-target-class` 為 `false` 或省略該屬性,則建立標準的 JDK 基於介面的代理。(有關不同代理型別的詳細討論,請參見代理機制。)

order

order

Ordered.LOWEST_PRECEDENCE

定義應用於帶有 `@Transactional` 註解的 Bean 的事務通知的順序。(有關 AOP 通知排序規則的更多資訊,請參見通知排序。)未指定順序意味著 AOP 子系統確定通知的順序。

處理 `@Transactional` 註解的預設通知模式是 `proxy`,它只允許攔截透過代理進行的呼叫。同一類內部的本地呼叫無法以這種方式攔截。對於更高階的攔截模式,考慮切換到 `aspectj` 模式並結合編譯時或載入時編織。
proxy-target-class 屬性控制為帶有 `@Transactional` 註解的類建立哪種型別的事務代理。如果 `proxy-target-class` 設定為 `true`,則建立基於類的代理。如果 `proxy-target-class` 為 `false` 或省略該屬性,則建立標準的 JDK 基於介面的代理。(有關不同代理型別的討論,請參見代理機制。)
@EnableTransactionManagement<tx:annotation-driven/> 只在其定義所在的同一個應用上下文中查詢帶有 `@Transactional` 的 Bean。這意味著,如果您將註解驅動的配置放在 DispatcherServletWebApplicationContext 中,它只會檢查控制器中的 `@Transactional` Bean,而不會檢查服務中的。有關更多資訊,請參見MVC

在評估方法的事務設定時,最具體的(most derived)位置優先。在以下示例中,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

字串標籤陣列,用於為事務新增表達性描述。

標籤可以由事務管理器評估,以將特定於實現的行為與實際事務關聯起來。

propagation

enumPropagation

可選的傳播設定。

isolation

enumIsolation

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

timeout

int(以秒為單位)

可選的事務超時。僅適用於傳播值為 REQUIREDREQUIRES_NEW

timeoutString

String(以秒為單位)

另一種指定 timeout(以秒為單位)為 String 值的方法,例如作為佔位符。

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,以確保即使在發生(可能是意外的)checked exception 時也能保持一致的回滾語義。此外,對於完全不強制執行 checked exception 的 Kotlin 應用程式,也建議進行此切換。

目前,您無法顯式控制事務的名稱,其中“名稱”是指在事務監視器和日誌輸出中顯示的事務名稱。對於宣告式事務,事務名稱始終是事務性增強類的完全限定類名 + . + 方法名。例如,如果 BusinessService 類的 handlePayment(..) 方法啟動了一個事務,則事務的名稱將是 com.example.BusinessService.handlePayment

使用 @Transactional 的多個事務管理器

大多數 Spring 應用程式只需要一個事務管理器,但有時您可能希望在單個應用程式中有多個獨立的事務管理器。您可以使用 @Transactional 註解的 valuetransactionManager 屬性來可選地指定要使用的 TransactionManager 的身份。這可以是 bean 名稱或事務管理器 bean 的 qualifier(限定符)值。例如,使用限定符表示法,您可以將以下 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" 時,它既可以用於自動裝配(識別訂單 repository),也可以用於事務管理器選擇,只要用於自動裝配的目標 bean 以及相關的事務管理器定義聲明瞭相同的限定符值。這種限定符值只需要在型別匹配的 bean 集合中是唯一的,而無需用作 ID。

自定義組合註解

如果您發現您在許多不同的方法上反覆使用 @Transactional 的相同屬性,Spring 的元註解支援 允許您為特定的用例定義自定義組合註解。例如,考慮以下註解定義

  • 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() {
		// ...
	}
}

在上面的示例中,我們使用了語法來定義事務管理器限定符和事務標籤,但我們也可以包含傳播行為、回滾規則、超時和其他特性。