声明式事务实现示例
考虑以下接口及其实现。此示例使用 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
)。然后,您通过使用一个顾问将切入点与 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
可以在事务进行中但尚未完成时发出数据。因此,依赖于整个事务成功完成的方法需要确保事务完成,并在调用代码中缓冲结果。