使用 @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`
如果您希望在不同型別的代理之間對方法可見性進行一致處理(這在 5.3 版本之前是預設設定),請考慮指定
Spring TestContext 框架也預設支援非私有的 `@Transactional` 測試方法。有關示例,請參閱測試章節中的事務管理。 |
您可以將 `@Transactional` 註解應用於介面定義、介面上的方法、類定義或類上的方法。然而,僅存在 `@Transactional` 註解不足以啟用事務行為。`@Transactional` 註解只是一種元資料,可以由相應的執行時基礎設施消費,該基礎設施利用這些元資料來配置具有事務行為的適當 Bean。在前面的示例中,`<tx:annotation-driven/>` 元素在執行時開啟了實際的事務管理。
Spring 團隊建議您使用 `@Transactional` 註解來註解具體類的方法,而不是依賴於介面中註解的方法,儘管後者在 5.0 版本後對基於介面和基於目標類的代理都有效。由於 Java 註解不會從介面繼承,因此在使用 AspectJ 模式時,編織基礎設施仍然不識別介面中宣告的註解,所以切面不會被應用。因此,您的事務註解可能會被靜默忽略:您的程式碼可能看起來“正常工作”,直到您測試回滾場景。 |
在代理模式下(這是預設模式),只有透過代理進入的外部方法呼叫才會被攔截。這意味著自呼叫(實際上是目標物件中的一個方法呼叫目標物件的另一個方法)在執行時不會導致實際的事務,即使被呼叫的方法標記了 `@Transactional`。此外,代理必須完全初始化才能提供預期的行為,因此您不應在初始化程式碼中依賴此特性,例如在 `@PostConstruct` 方法中。 |
如果您希望自呼叫也被事務包裝,請考慮使用 AspectJ 模式(參見下表中的 `mode` 屬性)。在這種情況下,首先沒有代理。相反,目標類會被編織(即其位元組碼被修改),以支援任何型別的方法上的 `@Transactional` 執行時行為。
XML 屬性 | 註解屬性 | 預設值 | 描述 |
---|---|---|---|
|
不適用 (參見 |
|
要使用的事務管理器的名稱。僅當事務管理器的名稱不是 |
|
|
|
預設模式(`proxy`)使用 Spring 的 AOP 框架處理要透過代理進行代理的帶註解 Bean(遵循代理語義,如前所述,僅應用於透過代理進入的方法呼叫)。替代模式(`aspectj`)則使用 Spring 的 AspectJ 事務切面編織受影響的類,修改目標類的位元組碼以應用於任何型別的方法呼叫。AspectJ 編織需要在類路徑中包含 |
|
|
|
僅適用於 `proxy` 模式。控制為帶有 `@Transactional` 註解的類建立哪種型別的事務代理。如果將 `proxy-target-class` 屬性設定為 `true`,則建立基於類的代理。如果 `proxy-target-class` 為 `false` 或省略該屬性,則建立標準的 JDK 基於介面的代理。(有關不同代理型別的詳細討論,請參見代理機制。) |
|
|
|
定義應用於帶有 `@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。這意味著,如果您將註解驅動的配置放在 DispatcherServlet 的 WebApplicationContext 中,它只會檢查控制器中的 `@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
。 -
事務是讀寫模式。
-
事務超時預設為底層事務系統的預設超時,如果不支援超時則為無。
-
任何
RuntimeException
或Error
會觸發回滾,而任何檢查型Exception
則不會。
您可以更改這些預設設定。下表總結了 `@Transactional` 註解的各種屬性
屬性 | 型別 | 描述 |
---|---|---|
|
可選的限定符,用於指定要使用的事務管理器。 |
|
|
|
|
|
字串標籤陣列,用於為事務新增表達性描述。 |
標籤可以由事務管理器評估,以將特定於實現的行為與實際事務關聯起來。 |
|
可選的傳播設定。 |
|
|
|
可選的隔離級別。僅適用於傳播值為 |
|
|
可選的事務超時。僅適用於傳播值為 |
|
|
另一種指定 |
|
|
讀寫與只讀事務。僅適用於傳播值為 |
|
|
可選的異常型別陣列,必須導致回滾。 |
|
異常名稱模式陣列。 |
可選的異常名稱模式陣列,必須導致回滾。 |
|
|
可選的異常型別陣列,不能導致回滾。 |
|
異常名稱模式陣列。 |
可選的異常名稱模式陣列,不能導致回滾。 |
有關回滾規則語義、模式以及基於模式的回滾規則可能無意匹配的警告,請參見回滾規則以獲取更多詳細資訊。 |
自 6.2 版本起,您可以全域性更改預設的回滾行為——例如,透過 `@EnableTransactionManagement(rollbackOn=ALL_EXCEPTIONS)`,這將導致事務中丟擲的所有異常(包括任何檢查型異常)都觸發回滾。如需進一步定製,`AnnotationTransactionAttributeSource` 提供了 `addDefaultRollbackRule(RollbackRuleAttribute)` 方法用於自定義預設規則。 請注意,事務特定的回滾規則會覆蓋預設行為,但對於未指定的異常會保留所選的預設行為。Spring 的 除非您依賴具有提交行為的 EJB 風格的業務異常,否則建議切換到 |
目前,您無法顯式控制事務的名稱,其中“名稱”是指在事務監視器和日誌輸出中顯示的事務名稱。對於宣告式事務,事務名稱始終是事務性增強類的完全限定類名 + .
+ 方法名。例如,如果 BusinessService
類的 handlePayment(..)
方法啟動了一個事務,則事務的名稱將是 com.example.BusinessService.handlePayment
。
使用 @Transactional
的多個事務管理器
大多數 Spring 應用程式只需要一個事務管理器,但有時您可能希望在單個應用程式中有多個獨立的事務管理器。您可以使用 @Transactional
註解的 value
或 transactionManager
屬性來可選地指定要使用的 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
上的各個方法在不同的事務管理器下執行,這些事務管理器由 order
、account
和 reactive-account
限定符區分。如果找不到特定限定符的 TransactionManager
bean,則仍然使用預設的 <tx:annotation-driven>
目標 bean 名稱 transactionManager
。
如果同一個類上的所有事務性方法共享同一個限定符,則考慮宣告一個類級別的 這種類級別的限定符可以宣告在具體類上,也適用於基類中的事務定義。這有效地覆蓋了任何未限定的基類方法的預設事務管理器選擇。 最後同樣重要的是,這種類級別的 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() {
// ...
}
}
在上面的示例中,我們使用了語法來定義事務管理器限定符和事務標籤,但我們也可以包含傳播行為、回滾規則、超時和其他特性。