使用 @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 配置中,`
<!-- 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`,則可以省略 ` |
響應式事務方法使用響應式返回型別,與指令式程式設計安排形成對比,如下面的清單所示
-
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`
Spring TestContext Framework 預設也支援非私有的 `@Transactional` 測試方法。有關示例,請參閱測試章節中的事務管理。 |
您可以將 `@Transactional` 註解應用於介面定義、介面上的方法、類定義或類上的方法。但是,僅僅存在 `@Transactional` 註解不足以啟用事務行為。`@Transactional` 註解只是元資料,可以由相應的執行時基礎設施使用,該基礎設施使用該元資料為適當的 bean 配置事務行為。在前面的示例中,`
| Spring 團隊建議您使用 `@Transactional` 註解來註解具體類的方法,而不是依賴介面中註解的方法,即使後者在 5.0 版本中適用於基於介面和目標類的代理。由於 Java 註解不能從介面繼承,因此在使用 AspectJ 模式時,編織基礎設施仍然無法識別介面中宣告的註解,因此切面不會被應用。因此,您的事務註解可能會被默默地忽略:您的程式碼可能看起來“正常工作”,直到您測試回滾場景。 |
| 在代理模式(預設)下,只有透過代理傳入的外部方法呼叫才會被攔截。這意味著自我呼叫(實際上是目標物件內的一個方法呼叫目標物件的另一個方法)在執行時不會導致實際的事務,即使被呼叫的方法標記有 `@Transactional`。此外,代理必須完全初始化才能提供預期的行為,因此您不應在初始化程式碼中依賴此功能,例如在 `@PostConstruct` 方法中。 |
如果您希望自呼叫也能被事務包裝,請考慮使用 AspectJ 模式(請參閱下表中的 `mode` 屬性)。在這種情況下,首先沒有代理。相反,目標類會被編織(即,其位元組碼被修改)以支援任何型別方法的 `@Transactional` 執行時行為。
| XML 屬性 | 註解屬性 | 預設值 | 描述 |
|---|---|---|---|
|
N/A (請參閱 `TransactionManagementConfigurer` javadoc) |
|
要使用的事務管理器的名稱。僅當事務管理器的名稱不是 `transactionManager` 時才需要,如前面的示例所示。 |
|
|
|
預設模式(`proxy`)使用 Spring 的 AOP 框架處理被註解的 Bean 進行代理(遵循前面討論的代理語義,僅適用於透過代理傳入的方法呼叫)。另一種模式(`aspectj`)則使用 Spring 的 AspectJ 事務切面編織受影響的類,修改目標類的位元組碼以應用於任何型別的方法呼叫。AspectJ 編織需要類路徑中包含 `spring-aspects.jar`,並且需要啟用載入時編織(或編譯時編織)。 (有關如何設定載入時編織的詳細資訊,請參閱Spring 配置。) |
|
|
|
僅適用於 `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` 和 ` |
在評估方法的事務設定時,最派生的位置優先。在以下示例中,`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` 註解的各種屬性
| 財產 | 型別 | 描述 |
|---|---|---|
|
可選的限定符,用於指定要使用的事務管理器。 |
|
|
|
`value` 的別名。 |
|
字串標籤陣列,用於為事務新增富有表達力的描述。 |
標籤可以由事務管理器評估,以將特定於實現的行為與實際事務關聯起來。 |
`enum`: `Propagation` |
可選傳播設定。 |
|
|
`enum`: `Isolation` |
可選隔離級別。僅適用於傳播值為 `REQUIRED` 或 `REQUIRES_NEW`。 |
|
|
可選事務超時。僅適用於傳播值為 `REQUIRED` 或 `REQUIRES_NEW`。 |
|
`String`(以秒為單位) |
用於以 `String` 值指定 `timeout`(以秒為單位)的替代方法,例如作為佔位符。 |
|
|
讀寫與只讀事務。僅適用於 `REQUIRED` 或 `REQUIRES_NEW` 的值。 |
|
`Class` 物件陣列,必須派生自 `Throwable.` |
必須導致回滾的異常型別的可選陣列。 |
|
異常名稱模式陣列。 |
必須導致回滾的異常名稱模式的可選陣列。 |
|
`Class` 物件陣列,必須派生自 `Throwable.` |
不應導致回滾的異常型別的可選陣列。 |
|
異常名稱模式陣列。 |
不應導致回滾的異常名稱模式的可選陣列。 |
| 有關回滾規則語義、模式以及對基於模式的回滾規則可能出現的意外匹配的警告的更多詳細資訊,請參閱回滾規則。 |
|
從 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` 註解的 `value` 或 `transactionManager` 屬性來可選地指定要使用的 `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` 上的各個方法在獨立的事務管理器下執行,透過 `order`、`account` 和 `reactive-account` 限定符進行區分。如果未找到特定的限定符 `TransactionManager` bean,則仍使用預設的 `
|
如果同一類上的所有事務方法共享相同的限定符,請考慮宣告一個型別級別的 `org.springframework.beans.factory.annotation.Qualifier` 註解。如果其值與特定事務管理器的限定符值(或 bean 名稱)匹配,則該事務管理器將用於沒有 `@Transactional` 本身特定限定符的事務定義。 這種型別級別的限定符可以在具體類上宣告,也適用於基類中的事務定義。這有效地覆蓋了任何不合格基類方法的預設事務管理器選擇。 最後但同樣重要的是,這種型別級別的 bean 限定符可以服務於多種目的,例如,值為“order”時,它可以用於自動裝配(識別訂單儲存庫)以及事務管理器選擇,只要自動裝配的目標 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() {
// ...
}
}
在前面的示例中,我們使用了語法來定義事務管理器限定符和事務標籤,但我們也可以包含傳播行為、回滾規則、超時和其他功能。