跳到主要内容
版本:7.0.3

声明式事务实现的示例

Hunyuan 7b 中英对照 Example of Declarative Transaction Implementation

考虑以下接口及其相应的实现。在这个示例中,我们使用FooBar类作为占位符,以便您可以专注于事务的使用,而不必关注特定的领域模型。对于这个示例而言,DefaultFooService类在每个实现的方法体中抛出UnsupportedOperationException实例这一事实是合适的。这种行为可以让您看到事务的创建,然后在遇到UnsupportedOperationException实例时回滚这些事务。以下列表显示了FooService接口:

// 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);

}

以下示例展示了前述接口的实现:

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

假设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>

检查前面的配置。它假设你希望将服务对象fooServicebean设置为事务性的。要应用的事务语义被封装在<tx:advice/>定义中。的定义意味着“所有以get开头的方法都应在只读事务的上下文中运行,而其他所有方法则按照默认的事务语义运行”。标签的transaction-manager属性被设置为将驱动事务的TransactionManagerbean的名称(在本例中,是txManager`bean)。

提示

如果你想要连接的TransactionManager的bean名称就是transactionManager,那么你可以在事务建议(<tx:advice/>)中省略transaction-manager属性。如果该TransactionManager bean的名称与其他名称不同,那么你必须显式使用transaction-manager属性,如前面的示例所示。

<aop:config/> 的定义确保了由 txAdvice bean 定义的事务建议在程序的适当位置被执行。首先,你定义一个切点(pointcut),该切点与 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 部分

现在我们已经分析了配置,你可能会问自己:“所有这些配置到底有什么作用呢?”

前面展示的配置用于在从fooServicebean定义创建的对象周围创建一个事务代理。该代理被配置了事务建议(transactional advice),因此当在代理上调用相应的方法时,会根据与该方法关联的事务配置来启动、挂起、标记为只读等操作。请考虑以下程序,该程序用于测试前面展示的配置:

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());
}
}

运行上述程序后产生的输出应类似于以下内容(为清晰起见,Log4J的输出以及DefaultFooService类的insertFoo(..)方法抛出的UnsupportedOperationException的堆栈跟踪信息已被截断):

<!-- 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 来判断一个方法的返回类型是否是反应式的(reactive)。

以下列表显示了之前使用的FooService的修改版本,但这次代码使用了反应式类型:

// 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);

}

以下示例展示了前述接口的实现:

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

命令式和响应式事务管理在事务边界及事务属性定义上具有相同的语义。命令式事务与响应式事务之间的主要区别在于后者的延迟执行特性。TransactionInterceptor会为返回的响应式类型添加一个事务操作符,以开始和清理事务。因此,调用一个事务性响应式方法会将实际的事务管理任务推迟到订阅类型中,由该订阅类型来激活对响应式类型的处理。

反应式事务管理的另一个方面与数据泄露有关,这是编程模型的一种自然结果。

命令式事务的方法返回值是在事务方法成功终止时返回的,这样部分计算的结果就不会从方法封闭作用域中泄漏出去。

反应式事务方法返回一种反应式包装器类型,该类型代表一个计算序列,并承诺开始并完成该计算。

在事务进行中(但不一定已经完成时),Publisher 可以发送数据。因此,那些依赖于整个事务成功完成的方法需要确保事务的完成,并将结果缓存到调用代码中。