Example of Declarative Transaction Implementation
考虑以下接口及其随附实现。此示例使用 Foo
和 Bar
类作为占位符,以便您专注于事务使用,而不必关注特定领域模型。对于本示例而言,DefaultFooService
类在每个已实现方法的主体中抛出 UnsupportedOperationException
实例的事实很好。通过该行为,您可以看到事务正在创建,然后针对 UnsupportedOperationException
实例进行回滚。以下清单显示 FooService
接口:
Consider the following interface and its attendant implementation. This example uses
Foo
and Bar
classes as placeholders so that you can concentrate on the transaction
usage without focusing on a particular domain model. For the purposes of this example,
the fact that the DefaultFooService
class throws UnsupportedOperationException
instances in the body of each implemented method is good. That behavior lets you see
transactions being created and then rolled back in response to the
UnsupportedOperationException
instance. The following listing shows the FooService
interface:
-
Java
-
Kotlin
public interface FooService {
Foo getFoo(String fooName);
Foo getFoo(String fooName, String barName);
void insertFoo(Foo foo);
void updateFoo(Foo foo);
}
interface FooService {
fun getFoo(fooName: String): Foo
fun getFoo(fooName: String, barName: String): Foo
fun insertFoo(foo: Foo)
fun updateFoo(foo: Foo)
}
以下示例显示了前面接口的实现:
The following example shows an implementation of the preceding interface:
-
Java
-
Kotlin
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) {
// ...
}
}
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)
必须在具有读写语义的事务上下文中运行。以下配置将在接下来的几段中详细说明:
Assume that the first two methods of the FooService
interface, getFoo(String)
and
getFoo(String, String)
, must run in the context of a transaction with read-only
semantics and that the other methods, insertFoo(Foo)
and updateFoo(Foo)
, must
run in the context of a transaction with read-write semantics. The following
configuration is explained in detail in the next few paragraphs:
<!-- 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)。
Examine the preceding configuration. It assumes that you want to make a service object,
the fooService
bean, transactional. The transaction semantics to apply are encapsulated
in the <tx:advice/>
definition. The <tx:advice/>
definition reads as "all methods
starting with get
are to run in the context of a read-only transaction, and all
other methods are to run with the default transaction semantics". The
transaction-manager
attribute of the <tx:advice/>
tag is set to the name of the
TransactionManager
bean that is going to drive the transactions (in this case, the
txManager
bean).
如果您要连接的 |
You can omit the |
<aop:config/>
定义确保 txAdvice
bean 定义的事务建议在程序的适当点运行。首先,您定义一个与 FooService
接口中定义的任何操作执行匹配的切入点(fooServiceOperation
)。然后,您使用顾问将切入点与 txAdvice
关联。结果表明,在执行 fooServiceOperation
时,将运行 txAdvice
定义的建议。
The <aop:config/>
definition ensures that the transactional advice defined by the
txAdvice
bean runs at the appropriate points in the program. First, you define a
pointcut that matches the execution of any operation defined in the FooService
interface
(fooServiceOperation
). Then you associate the pointcut with the txAdvice
by using an
advisor. The result indicates that, at the execution of a fooServiceOperation
,
the advice defined by txAdvice
is run.
<aop:pointcut/>
元素中定义的表达式是一个 AspectJ 切入点表达式。请参阅 the AOP section 以了解有关 Spring 中切入点表达式的更多详细信息。
The expression defined within the <aop:pointcut/>
element is an AspectJ pointcut
expression. See the AOP section for more details on pointcut
expressions in Spring.
一个常见要求是让整个服务层具有事务性。最好的方法是更改切入点表达式以匹配服务层中的任何操作。以下示例显示了如何做到这一点:
A common requirement is to make an entire service layer transactional. The best way to do this is to change the pointcut expression to match any operation in your service layer. The following example shows how to do so:
<aop:config>
<aop:pointcut id="fooServiceMethods" expression="execution(* x.y.service.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceMethods"/>
</aop:config>
在前面的示例中,假设您的所有服务接口都在 |
In the preceding example, it is assumed that all your service interfaces are defined
in the |
现在我们已经分析了配置,您可能会问自己,“所有这些配置实际上做了什么?”
Now that we have analyzed the configuration, you may be asking yourself, "What does all this configuration actually do?"
前面显示的配置用于在从 fooService
bean 定义创建的对象周围创建一个事务代理。对代理配置了事务建议,以便在对代理调用适当的方法时,启动、暂停和标记事务为只读等等,具体取决于与该方法关联的事务配置。请考虑以下程序,它将对前面显示的配置进行测试驱动:
The configuration shown earlier is used to create a transactional proxy around the object
that is created from the fooService
bean definition. The proxy is configured with
the transactional advice so that, when an appropriate method is invoked on the proxy,
a transaction is started, suspended, marked as read-only, and so on, depending on the
transaction configuration associated with that method. Consider the following program
that test drives the configuration shown earlier:
-
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())
}
运行前一个程序的输出应类似于以下内容(UnsupportedOperationException
由 DefaultFooService
类中的 insertFoo(..)
方法抛出,其 Log4J 输出和堆栈跟踪已被截断以提高清晰度):
The output from running the preceding program should resemble the following (the Log4J
output and the stack trace from the UnsupportedOperationException
thrown by the
insertFoo(..)
method of the DefaultFooService
class have been truncated for clarity):
<!-- 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)
要使用反应式事务管理,代码必须使用反应式类型。
To use reactive transaction management the code has to use reactive types.
Spring Framework 使用 |
Spring Framework uses the |
以下列表显示了之前使用的 FooService
的修改版本,但这次代码使用了反应式类型:
The following listing shows a modified version of the previously used FooService
, but
this time the code uses reactive types:
-
Java
-
Kotlin
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);
}
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>
}
以下示例显示了前面接口的实现:
The following example shows an implementation of the preceding interface:
-
Java
-
Kotlin
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) {
// ...
}
}
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
用一个事务操作符装饰返回的反应式类型,以启动和清除事务。因此,调用一个事务反应式方法会将实际的事务管理延迟到激活反应式类型处理的订阅类型。
Imperative and reactive transaction management share the same semantics for transaction
boundary and transaction attribute definitions. The main difference between imperative
and reactive transactions is the deferred nature of the latter. TransactionInterceptor
decorates the returned reactive type with a transactional operator to begin and clean up
the transaction. Therefore, calling a transactional reactive method defers the actual
transaction management to a subscription type that activates processing of the reactive
type.
反应式事务管理的另一个方面涉及数据逃逸,这是编程模型的自然结果。
Another aspect of reactive transaction management relates to data escaping which is a natural consequence of the programming model.
命令式事务的方法返回值在方法成功终止后从事务方法返回,以便部分计算的结果不会逃逸方法闭包。
Method return values of imperative transactions are returned from transactional methods upon successful termination of a method so that partially computed results do not escape the method closure.
反应式事务方法返回一个反应式包装器类型,它表示一个计算序列以及对开始和完成计算的承诺。
Reactive transaction methods return a reactive wrapper type which represents a computation sequence along with a promise to begin and complete the computation.
Publisher
可以在事务正在进行时(但不一定是已完成时)发出数据。因此,依赖于整个事务成功完成的方法需要确保完成并在调用代码中缓冲结果。
A Publisher
can emit data while a transaction is ongoing but not necessarily completed.
Therefore, methods that depend upon successful completion of an entire transaction need
to ensure completion and buffer results in the calling code.