对于最新的稳定版本,请使用 Spring Framework 6.2.4spring-doc.cadn.net.cn

声明式事务实现示例

请考虑以下接口及其附带的实现。此示例使用FooBar类作为占位符,以便您可以专注于事务 使用,而不关注特定的域模型。在本示例中, 事实上,DefaultFooService类引发UnsupportedOperationException实例是好的。该行为让您看到 事务创建,然后回滚以响应UnsupportedOperationException实例。下面的清单显示了FooService接口:spring-doc.cadn.net.cn

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

以下示例显示了上述接口的实现:spring-doc.cadn.net.cn

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)必须在只读的事务上下文中运行 semantics 和其他方法insertFoo(Foo)updateFoo(Foo)必须 在具有读写语义的事务上下文中运行。以下内容 接下来的几段将详细解释配置:spring-doc.cadn.net.cn

<!-- 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,transactional。要应用的事务语义是封装的 在<tx:advice/>定义。这<tx:advice/>定义读作 “所有方法 起始于get在只读事务的上下文中运行,并且所有 其他方法是使用默认的事务语义运行”。这transaction-manager属性的<tx:advice/>标记设置为TransactionManagerbean 来驱动事务(在本例中为txManagerbean) 的 Bean 的spring-doc.cadn.net.cn

您可以省略transaction-manager属性 (<tx:advice/>) 如果TransactionManager您想要 wire in 的名称为transactionManager.如果TransactionManagerbean 那个 you want to wire in 具有任何其他名称,则必须使用transaction-manager属性,如前面的示例所示。

<aop:config/>定义确保由txAdviceBean 在程序中的适当点运行。首先,定义一个 切入点,该切入点与FooService接口 (fooServiceOperation).然后,将切入点与txAdvice通过使用 顾问。结果表明,在执行fooServiceOperation, 由txAdvice运行。spring-doc.cadn.net.cn

<aop:pointcut/>元素是一个 AspectJ 切入点 表达。有关 pointcut 的更多详细信息,请参阅 AOP 部分 表达式。spring-doc.cadn.net.cn

一个常见的要求是使整个服务层具有事务性。最好的方法 执行此作是为了更改切入点表达式以匹配 service 层。以下示例显示了如何执行此作:spring-doc.cadn.net.cn

<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 部分

现在我们已经分析了配置,您可能会问自己, “所有这些配置实际上是做什么的?”spring-doc.cadn.net.cn

前面显示的配置用于围绕对象创建事务代理 ,它是从fooServicebean 定义。代理配置了 事务性通知,以便在代理上调用适当的方法时, 事务被启动、暂停、标记为只读等,具体取决于 transaction 配置。请考虑以下程序 该测试驱动前面显示的配置:spring-doc.cadn.net.cn

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

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):spring-doc.cadn.net.cn

<!-- 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-doc.cadn.net.cn

Spring Framework uses the ReactiveAdapterRegistry to determine whether a method return type is reactive.

The following listing shows a modified version of the previously used FooService, but this time the code uses reactive types:spring-doc.cadn.net.cn

// 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>
}

The following example shows an implementation of the preceding interface:spring-doc.cadn.net.cn

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

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.spring-doc.cadn.net.cn

Another aspect of reactive transaction management relates to data escaping which is a natural consequence of the programming model.spring-doc.cadn.net.cn

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.spring-doc.cadn.net.cn

Reactive transaction methods return a reactive wrapper type which represents a computation sequence along with a promise to begin and complete the computation.spring-doc.cadn.net.cn

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.spring-doc.cadn.net.cn