宣告式事務實現示例
考慮以下介面及其伴隨實現。本示例使用 Foo 和 Bar 類作為佔位符,以便您可以專注於事務的使用而無需關注特定的領域模型。對於本示例,DefaultFooService 類在每個已實現方法的主體中丟擲 UnsupportedOperationException 例項這一事實是好的。這種行為讓您可以看到事務的建立,然後響應 UnsupportedOperationException 例項而回滾。以下列表顯示了 FooService 介面
-
Java
-
Kotlin
// the service interface that we want to make transactional
package x.y.service;
public interface FooService {
Foo getFoo(String fooName);
Foo getFoo(String fooName, String barName);
void insertFoo(Foo foo);
void updateFoo(Foo foo);
}
// the service interface that we want to make transactional
package x.y.service
interface FooService {
fun getFoo(fooName: String): Foo
fun getFoo(fooName: String, barName: String): Foo
fun insertFoo(foo: Foo)
fun updateFoo(foo: Foo)
}
以下示例顯示了上述介面的實現
-
Java
-
Kotlin
package x.y.service;
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) {
// ...
}
}
package x.y.service
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) {
// ...
}
}
假設 FooService 介面的前兩個方法 getFoo(String) 和 getFoo(String, String) 必須在具有隻讀語義的事務上下文中執行,而其他方法 insertFoo(Foo) 和 updateFoo(Foo) 必須在具有讀寫語義的事務上下文中執行。以下配置在接下來的幾段中詳細解釋
<!-- 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"/>
<!-- the transactional advice (what 'happens'; see the <aop:advisor/> bean below) -->
<tx:advice id="txAdvice" transaction-manager="txManager">
<!-- the transactional semantics... -->
<tx:attributes>
<!-- all methods starting with 'get' are read-only -->
<tx:method name="get*" read-only="true"/>
<!-- other methods use the default transaction settings (see below) -->
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!-- ensure that the above transactional advice runs for any execution
of an operation defined by the FooService interface -->
<aop:config>
<aop:pointcut id="fooServiceOperation" expression="execution(* x.y.service.FooService.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceOperation"/>
</aop:config>
<!-- don't forget the DataSource -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
<property name="url" value="jdbc:oracle:thin:@rj-t42:1521:elvis"/>
<property name="username" value="scott"/>
<property name="password" value="tiger"/>
</bean>
<!-- similarly, don't forget the TransactionManager -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- other <bean/> definitions here -->
</beans>
檢查上述配置。它假設您希望使服務物件(fooService bean)具有事務性。要應用的事務語義封裝在 <tx:advice/> 定義中。<tx:advice/> 定義表示“所有以 get 開頭的方法都在只讀事務的上下文中執行,所有其他方法都以預設事務語義執行”。<tx:advice/> 標籤的 transaction-manager 屬性設定為將驅動事務的 TransactionManager bean 的名稱(在本例中為 txManager bean)。
如果希望連線的 TransactionManager bean 的名稱為 transactionManager,則可以省略事務建議(<tx:advice/>)中的 transaction-manager 屬性。如果希望連線的 TransactionManager bean 具有任何其他名稱,則必須顯式使用 transaction-manager 屬性,如上例所示。 |
<aop:config/> 定義確保 txAdvice bean 定義的事務建議在程式的適當點執行。首先,您定義一個匹配 FooService 介面(fooServiceOperation)中定義的任何操作執行的切入點。然後,您透過使用 Advisor 將切入點與 txAdvice 相關聯。結果表明,在執行 fooServiceOperation 時,將執行 txAdvice 定義的建議。
<aop:pointcut/> 元素中定義的表示式是 AspectJ 切入點表示式。有關 Spring 中切入點表示式的更多詳細資訊,請參閱AOP 部分。
一個常見的需求是使整個服務層具有事務性。最好的方法是更改切入點表示式以匹配服務層中的任何操作。以下示例顯示瞭如何做到這一點
<aop:config>
<aop:pointcut id="fooServiceMethods" expression="execution(* x.y.service.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceMethods"/>
</aop:config>
在前面的示例中,假定所有服務介面都定義在 x.y.service 包中。有關更多詳細資訊,請參閱AOP 部分。 |
現在我們已經分析了配置,您可能會問自己,“所有這些配置到底做了什麼?”
前面顯示的配置用於在從 fooService bean 定義建立的物件周圍建立一個事務代理。代理配置有事務建議,因此,當在代理上呼叫適當的方法時,事務會根據與該方法關聯的事務配置而啟動、掛起、標記為只讀等等。考慮以下測試驅動前面所示配置的程式
-
Java
-
Kotlin
public final class Boot {
public static void main(final String[] args) throws Exception {
ApplicationContext ctx = new ClassPathXmlApplicationContext("context.xml");
FooService fooService = ctx.getBean(FooService.class);
fooService.insertFoo(new Foo());
}
}
import org.springframework.beans.factory.getBean
fun main() {
val ctx = ClassPathXmlApplicationContext("context.xml")
val fooService = ctx.getBean<FooService>("fooService")
fooService.insertFoo(Foo())
}
執行上述程式後輸出應類似於以下內容(為清晰起見,已截斷 DefaultFooService 類的 insertFoo(..) 方法丟擲的 UnsupportedOperationException 的 Log4J 輸出和堆疊跟蹤)
<!-- the Spring container is starting up... -->
[AspectJInvocationContextExposingAdvisorAutoProxyCreator] - Creating implicit proxy for bean 'fooService' with 0 common interceptors and 1 specific interceptors
<!-- the DefaultFooService is actually proxied -->
[JdkDynamicAopProxy] - Creating JDK dynamic proxy for [x.y.service.DefaultFooService]
<!-- ... the insertFoo(..) method is now being invoked on the proxy -->
[TransactionInterceptor] - Getting transaction for x.y.service.FooService.insertFoo
<!-- the transactional advice kicks in here... -->
[DataSourceTransactionManager] - Creating new transaction with name [x.y.service.FooService.insertFoo]
[DataSourceTransactionManager] - Acquired Connection [org.apache.commons.dbcp.PoolableConnection@a53de4] for JDBC transaction
<!-- the insertFoo(..) method from DefaultFooService throws an exception... -->
[RuleBasedTransactionAttribute] - Applying rules to determine whether transaction should rollback on java.lang.UnsupportedOperationException
[TransactionInterceptor] - Invoking rollback for transaction on x.y.service.FooService.insertFoo due to throwable [java.lang.UnsupportedOperationException]
<!-- and the transaction is rolled back (by default, RuntimeException instances cause rollback) -->
[DataSourceTransactionManager] - Rolling back JDBC transaction on Connection [org.apache.commons.dbcp.PoolableConnection@a53de4]
[DataSourceTransactionManager] - Releasing JDBC Connection after transaction
[DataSourceUtils] - Returning JDBC Connection to DataSource
Exception in thread "main" java.lang.UnsupportedOperationException at x.y.service.DefaultFooService.insertFoo(DefaultFooService.java:14)
<!-- AOP infrastructure stack trace elements removed for clarity -->
at $Proxy0.insertFoo(Unknown Source)
at Boot.main(Boot.java:11)
要使用反應式事務管理,程式碼必須使用反應式型別。
Spring Framework 使用 ReactiveAdapterRegistry 來確定方法返回型別是否是反應式型別。 |
以下列表顯示了先前使用的 FooService 的修改版本,但這次程式碼使用了反應式型別
-
Java
-
Kotlin
// the reactive service interface that we want to make transactional
package x.y.service;
public interface FooService {
Flux<Foo> getFoo(String fooName);
Publisher<Foo> getFoo(String fooName, String barName);
Mono<Void> insertFoo(Foo foo);
Mono<Void> updateFoo(Foo foo);
}
// the reactive service interface that we want to make transactional
package x.y.service
interface FooService {
fun getFoo(fooName: String): Flow<Foo>
fun getFoo(fooName: String, barName: String): Publisher<Foo>
fun insertFoo(foo: Foo) : Mono<Void>
fun updateFoo(foo: Foo) : Mono<Void>
}
以下示例顯示了上述介面的實現
-
Java
-
Kotlin
package x.y.service;
public class DefaultFooService implements FooService {
@Override
public Flux<Foo> getFoo(String fooName) {
// ...
}
@Override
public Publisher<Foo> getFoo(String fooName, String barName) {
// ...
}
@Override
public Mono<Void> insertFoo(Foo foo) {
// ...
}
@Override
public Mono<Void> updateFoo(Foo foo) {
// ...
}
}
package x.y.service
class DefaultFooService : FooService {
override fun getFoo(fooName: String): Flow<Foo> {
// ...
}
override fun getFoo(fooName: String, barName: String): Publisher<Foo> {
// ...
}
override fun insertFoo(foo: Foo): Mono<Void> {
// ...
}
override fun updateFoo(foo: Foo): Mono<Void> {
// ...
}
}
命令式和反應式事務管理在事務邊界和事務屬性定義方面共享相同的語義。命令式事務和反應式事務之間的主要區別在於後者的延遲特性。TransactionInterceptor 用事務運算子裝飾返回的反應式型別,以開始和清理事務。因此,呼叫事務性反應式方法將實際的事務管理推遲到啟用反應式型別處理的訂閱型別。
反應式事務管理的另一個方面與資料逃逸有關,這是程式設計模型的自然結果。
命令式事務的方法返回值在方法成功終止時從事務方法返回,這樣部分計算結果就不會逃逸方法閉包。
反應式事務方法返回一個反應式包裝型別,它表示一個計算序列以及開始和完成計算的承諾。
當事務正在進行但尚未完成時,Publisher 可以發出資料。因此,依賴於整個事務成功完成的方法需要確保完成並在呼叫程式碼中緩衝結果。