使用 @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
如果您希望在不同種類的 Proxy 中一致地處理方法可見性(這是 5.3 之前的預設值),請考慮指定
Spring TestContext Framework 也預設支援非私有的 |
您可以將 @Transactional
註解套用至介面定義、介面上的一個方法、類別定義或類別上的一個方法。但是,僅僅存在 @Transactional
註解不足以啟動交易行為。@Transactional
註解僅僅是元資料,可由相應的執行階段基礎架構使用,該基礎架構使用該元資料來組態具有交易行為的適當 Bean。在前面的範例中,<tx:annotation-driven/>
元素開啟了執行階段的實際交易管理。
Spring 團隊建議您使用 @Transactional 註解來註解具體類別的方法,而不是依賴介面中的註解方法,即使後者對於基於介面的 Proxy 和目標類別 Proxy 在 5.0 及更高版本中有效。由於 Java 註解不會從介面繼承,因此在使用 AspectJ 模式時,編織基礎架構仍然無法辨識介面宣告的註解,因此不會套用切面。因此,您的交易註解可能會被靜默忽略:您的程式碼在您測試回滾情境之前可能會看起來「正常運作」。 |
在 Proxy 模式(預設模式)中,只有透過 Proxy 傳入的外部方法呼叫會被攔截。這表示自我調用(實際上,目標物件內的方法呼叫目標物件的另一個方法)即使調用的方法標記有 @Transactional ,也不會在執行階段導致實際交易。此外,Proxy 必須完全初始化才能提供預期的行為,因此您不應在初始化程式碼(例如,在 @PostConstruct 方法中)中依賴此功能。 |
如果您希望自我調用也以交易方式包裝,請考慮使用 AspectJ 模式(請參閱下表中的 mode
屬性)。在這種情況下,首先沒有 Proxy。相反,目標類別會被編織(也就是說,修改其位元組碼)以支援任何種類方法上的 @Transactional
執行階段行為。
XML 屬性 | 註解屬性 | 預設值 | 描述 |
---|---|---|---|
|
N/A(請參閱 |
|
要使用的交易管理器的名稱。只有在交易管理器的名稱不是 |
|
|
|
預設模式 ( |
|
|
|
僅適用於 |
|
|
|
定義套用至使用 |
處理 @Transactional 註解的預設建議模式是 proxy ,它僅允許攔截透過 Proxy 的呼叫。無法以這種方式攔截同一類別內的本機呼叫。如需更進階的攔截模式,請考慮切換到 aspectj 模式,並結合編譯時期或載入時期編織。 |
proxy-target-class 屬性控制為使用 @Transactional 註解的類別建立何種類型的交易 Proxy。如果 proxy-target-class 設定為 true ,則會建立基於類別的 Proxy。如果 proxy-target-class 為 false 或省略此屬性,則會建立標準 JDK 基於介面的 Proxy。(如需不同 Proxy 類型的討論,請參閱Proxy 機制。) |
@EnableTransactionManagement 和 <tx:annotation-driven/> 僅在定義它們的相同應用程式 Context 中尋找 @Transactional Bean。這表示,如果您在 DispatcherServlet 的 WebApplicationContext 中放置註解驅動的組態,它只會在您的控制器中檢查 @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
。 -
交易是讀寫的。
-
交易逾時預設為基礎交易系統的預設逾時,如果交易系統不支援逾時,則為無逾時。
-
任何
RuntimeException
或Error
都會觸發回滾,而任何已檢查的Exception
則不會。
您可以變更這些預設設定。下表總結了 @Transactional
註解的各種屬性
屬性 | 類型 | 描述 |
---|---|---|
|
可選的限定詞,用於指定要使用的交易管理器。 |
|
|
|
|
|
|
交易管理器可能會評估標籤,以將實作特定的行為與實際交易關聯起來。 |
|
可選的傳播設定。 |
|
|
|
可選的隔離等級。僅適用於 |
|
|
可選的交易逾時。僅適用於 |
|
|
以 |
|
|
讀寫與唯讀交易。僅適用於 |
|
|
必須導致回滾的可選例外類型陣列。 |
|
例外名稱模式陣列。 |
必須導致回滾的可選例外名稱模式陣列。 |
|
|
可選的例外類型陣列,不得導致回滾。 |
|
例外名稱模式陣列。 |
可選的例外名稱模式陣列,不得導致回滾。 |
如需有關回滾規則語意、模式以及關於基於模式的回滾規則可能發生的意外比對的警告的更多詳細資訊,請參閱回滾規則。 |
截至 6.2 版本,您可以全域變更預設的回滾行為 – 例如,透過 請注意,交易特定的回滾規則會覆寫預設行為,但會為未指定的例外狀況保留所選的預設行為。Spring 的 除非您依賴具有提交行為的 EJB 樣式業務例外,否則建議切換到 |
目前,您無法明確控制交易的名稱,此處的「名稱」指的是交易監視器和記錄輸出中顯示的交易名稱。對於宣告式交易,交易名稱始終是事務性建議類別的完整類別名稱 + .
+ 方法名稱。例如,如果 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,則仍然使用預設的 <tx:annotation-driven>
目標 bean 名稱 transactionManager
。
如果同一類別上的所有事務性方法都共用相同的限定詞,請考慮宣告類型層級的 這樣的類型層級限定詞可以在具體類別上宣告,也適用於來自基底類別的交易定義。這有效地覆寫了任何未限定的基底類別方法的預設交易管理器選擇。 最後但並非最不重要的是,這樣的類型層級 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() {
// ...
}
}
在前面的範例中,我們使用了語法來定義交易管理器限定詞和事務性標籤,但我們也可以包含傳播行為、回滾規則、逾時和其他功能。