Skip to content

编程式事务管理 (Programmatic Transaction Management)

Spring 框架提供了两种编程式事务管理的方法:

  • 使用 TransactionTemplateTransactionalOperator
  • 直接使用 TransactionManager 实现。

Spring 团队通常建议使用 TransactionTemplate 进行命令式流程的编程式事务管理,使用 TransactionalOperator 进行响应式代码的编程式事务管理。第二种方法类似于使用 JTA UserTransaction API,尽管异常处理不那么繁琐。

使用 TransactionTemplate

TransactionTemplate 采用了与其他 Spring 模板(如 JdbcTemplate)相同的方法。它使用一种回调机制(使应用程序代码免于必须执行繁琐的事务资源获取和释放),从而产生意图驱动的代码,即你的代码仅关注你想要做的事情。

注意

如下面的示例所示,使用 TransactionTemplate 绝对会将你与 Spring 的事务基础设施和 API 耦合在一起。编程式事务管理是否适合你的开发需求,是你必须自己做出的决定。

必须在事务上下文中运行并显式使用 TransactionTemplate 的应用程序代码类似于下一个示例。作为应用程序开发人员,你可以编写一个 TransactionCallback 实现(通常表示为匿名内部类),并在其中包含需要在事务上下文中运行的代码。然后,你可以将自定义 TransactionCallback 的实例传递给 TransactionTemplate 上公开的 execute(..) 方法。以下示例展示了如何执行此操作:

java
public class SimpleService implements Service {

	// 在此实例的所有方法之间共享的单个 TransactionTemplate
	private final TransactionTemplate transactionTemplate;

	// 使用构造函数注入来提供 PlatformTransactionManager
	public SimpleService(PlatformTransactionManager transactionManager) {
		this.transactionTemplate = new TransactionTemplate(transactionManager);
	}

	public Object someServiceMethod() {
		return transactionTemplate.execute(new TransactionCallback() {
			// 此方法中的代码在事务上下文中运行
			public Object doInTransaction(TransactionStatus status) {
				updateOperation1();
				return resultOfUpdateOperation2();
			}
		});
	}
}
kotlin
// 使用构造函数注入来提供 PlatformTransactionManager
class SimpleService(transactionManager: PlatformTransactionManager) : Service {

	// 在此实例的所有方法之间共享的单个 TransactionTemplate
	private val transactionTemplate = TransactionTemplate(transactionManager)

	fun someServiceMethod() = transactionTemplate.execute<Any?> {
		updateOperation1()
		resultOfUpdateOperation2()
	}
}

如果没有返回值,你可以使用带有匿名类的便捷 TransactionCallbackWithoutResult 类,如下所示:

java
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
	protected void doInTransactionWithoutResult(TransactionStatus status) {
		updateOperation1();
		updateOperation2();
	}
});
kotlin
transactionTemplate.execute(object : TransactionCallbackWithoutResult() {
	override fun doInTransactionWithoutResult(status: TransactionStatus) {
		updateOperation1()
		updateOperation2()
	}
})

回调中的代码可以通过在提供的 TransactionStatus 对象上调用 setRollbackOnly() 方法来回滚事务,如下所示:

java
transactionTemplate.execute(new TransactionCallbackWithoutResult() {

	protected void doInTransactionWithoutResult(TransactionStatus status) {
		try {
			updateOperation1();
			updateOperation2();
		} catch (SomeBusinessException ex) {
			status.setRollbackOnly();
		}
	}
});
kotlin
transactionTemplate.execute(object : TransactionCallbackWithoutResult() {

	override fun doInTransactionWithoutResult(status: TransactionStatus) {
		try {
			updateOperation1()
			updateOperation2()
		} catch (ex: SomeBusinessException) {
			status.setRollbackOnly()
		}
	}
})

指定事务设置

你可以通过编程方式或在配置中指定 TransactionTemplate 上的事务设置(例如传播模式、隔离级别、超时等)。默认情况下,TransactionTemplate 实例具有默认事务设置。以下示例显示了特定 TransactionTemplate 的事务设置的编程式自定义:

java
public class SimpleService implements Service {

	private final TransactionTemplate transactionTemplate;

	public SimpleService(PlatformTransactionManager transactionManager) {
		this.transactionTemplate = new TransactionTemplate(transactionManager);

		// 如果需要,也可以在这里显式设置事务设置
		this.transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_UNCOMMITTED);
		this.transactionTemplate.setTimeout(30); // 30 秒
		// 等等...
	}
}
kotlin
class SimpleService(transactionManager: PlatformTransactionManager) : Service {

	private val transactionTemplate = TransactionTemplate(transactionManager).apply {
		// 如果需要,也可以在这里显式设置事务设置
		isolationLevel = TransactionDefinition.ISOLATION_READ_UNCOMMITTED
		timeout = 30 // 30 秒
		// 等等...
	}
}

以下示例使用 Spring XML 配置定义了一个具有某些自定义事务设置的 TransactionTemplate

xml
<bean id="sharedTransactionTemplate"
		class="org.springframework.transaction.support.TransactionTemplate">
	<property name="isolationLevelName" value="ISOLATION_READ_UNCOMMITTED"/>
	<property name="timeout" value="30"/>
</bean>

然后,你可以将 sharedTransactionTemplate 注入到所需的任意数量的服务中。

最后,TransactionTemplate 类的实例是线程安全的,因为实例不维护任何会话状态。但是,TransactionTemplate 实例确实维护配置状态。因此,虽然许多类可以共享 TransactionTemplate 的单个实例,但如果类需要使用具有不同设置(例如,不同的隔离级别)的 TransactionTemplate,则需要创建两个不同的 TransactionTemplate 实例。

使用 TransactionalOperator

TransactionalOperator 遵循类似于其他响应式操作符的操作符设计。它使用一种回调方法(使应用程序代码免于必须执行繁琐的事务资源获取和释放),从而产生意图驱动的代码,即你的代码仅关注你想要做的事情。

注意

如下面的示例所示,使用 TransactionalOperator 绝对会将你与 Spring 的事务基础设施和 API 耦合在一起。编程式事务管理是否适合你的开发需求,是你必须自己做出的决定。

必须在事务上下文中运行并显式使用 TransactionalOperator 的应用程序代码类似于下一个示例:

java
public class SimpleService implements Service {

	// 在此实例的所有方法之间共享的单个 TransactionalOperator
	private final TransactionalOperator transactionalOperator;

	// 使用构造函数注入来提供 ReactiveTransactionManager
	public SimpleService(ReactiveTransactionManager transactionManager) {
		this.transactionalOperator = TransactionalOperator.create(transactionManager);
	}

	public Mono<Object> someServiceMethod() {

		// 此方法中的代码在事务上下文中运行

		Mono<Object> update = updateOperation1();

		return update.then(resultOfUpdateOperation2).as(transactionalOperator::transactional);
	}
}
kotlin
// 使用构造函数注入来提供 ReactiveTransactionManager
class SimpleService(transactionManager: ReactiveTransactionManager) : Service {

	// 在此实例的所有方法之间共享的单个 TransactionalOperator
	private val transactionalOperator = TransactionalOperator.create(transactionManager)

	suspend fun someServiceMethod() = transactionalOperator.executeAndAwait<Any?> {
		updateOperation1()
		resultOfUpdateOperation2()
	}
}

TransactionalOperator 可以通过两种方式使用:

  • 使用 Project Reactor 类型的操作符风格(mono.as(transactionalOperator::transactional)
  • 适用于其他所有情况的回调风格(transactionalOperator.execute(TransactionCallback<T>)

回调中的代码可以通过在提供的 ReactiveTransaction 对象上调用 setRollbackOnly() 方法来回滚事务,如下所示:

java
transactionalOperator.execute(new TransactionCallback<>() {

	public Mono<Object> doInTransaction(ReactiveTransaction status) {
		return updateOperation1().then(updateOperation2)
					.doOnError(SomeBusinessException.class, e -> status.setRollbackOnly());
		}
	}
});
kotlin
transactionalOperator.execute(object : TransactionCallback() {

	override fun doInTransactionWithoutResult(status: ReactiveTransaction) {
		updateOperation1().then(updateOperation2)
					.doOnError(SomeBusinessException.class, e -> status.setRollbackOnly())
	}
})

取消信号 (Cancel Signals)

在 Reactive Streams 中,Subscriber 可以取消其 Subscription 并停止其 Publisher。Project Reactor 中的操作符以及其他库(例如 next()take(long)timeout(Duration) 等)都可以发出取消信号。没有办法知道取消的原因,是由于错误还是仅仅是对消费更多内容缺乏兴趣。从 5.3 版开始,取消信号会导致回滚。因此,重要的是要考虑事务 Publisher 下游使用的操作符。特别是在 Flux 或其他多值 Publisher 的情况下,必须消费完整的输出才能使事务完成。

指定事务设置

你可以为 TransactionalOperator 指定事务设置(例如传播模式、隔离级别、超时等)。默认情况下,TransactionalOperator 实例具有默认事务设置。以下示例显示了特定 TransactionalOperator 的事务设置的自定义:

java
public class SimpleService implements Service {

	private final TransactionalOperator transactionalOperator;

	public SimpleService(ReactiveTransactionManager transactionManager) {
		DefaultTransactionDefinition definition = new DefaultTransactionDefinition();

		// 如果需要,也可以在这里显式设置事务设置
		definition.setIsolationLevel(TransactionDefinition.ISOLATION_READ_UNCOMMITTED);
		definition.setTimeout(30); // 30 秒
		// 等等...

		this.transactionalOperator = TransactionalOperator.create(transactionManager, definition);
	}
}
kotlin
class SimpleService(transactionManager: ReactiveTransactionManager) : Service {

	private val definition = DefaultTransactionDefinition().apply {
		// 如果需要,也可以在这里显式设置事务设置
		isolationLevel = TransactionDefinition.ISOLATION_READ_UNCOMMITTED
		timeout = 30 // 30 秒
		// 等等...
	}
	private val transactionalOperator = TransactionalOperator(transactionManager, definition)
}

使用 TransactionManager

以下各节解释了命令式和响应式事务管理器的编程式用法。

使用 PlatformTransactionManager

对于命令式事务,你可以直接使用 org.springframework.transaction.PlatformTransactionManager 来管理你的事务。为此,通过 bean 引用将你使用的 PlatformTransactionManager 的实现传递给你的 bean。然后,通过使用 TransactionDefinitionTransactionStatus 对象,你可以启动事务、回滚和提交。以下示例展示了如何执行此操作:

java
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
// 显式设置事务名称是只能以编程方式完成的事情
def.setName("SomeTxName");
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

TransactionStatus status = txManager.getTransaction(def);
try {
	// 将你的业务逻辑放在这里
} catch (MyException ex) {
	txManager.rollback(status);
	throw ex;
}
txManager.commit(status);
kotlin
val def = DefaultTransactionDefinition()
// 显式设置事务名称是只能以编程方式完成的事情
def.setName("SomeTxName")
def.propagationBehavior = TransactionDefinition.PROPAGATION_REQUIRED

val status = txManager.getTransaction(def)
try {
	// 将你的业务逻辑放在这里
} catch (ex: MyException) {
	txManager.rollback(status)
	throw ex
}

txManager.commit(status)

使用 ReactiveTransactionManager

在使用响应式事务时,你可以直接使用 org.springframework.transaction.ReactiveTransactionManager 来管理你的事务。为此,通过 bean 引用将你使用的 ReactiveTransactionManager 的实现传递给你的 bean。然后,通过使用 TransactionDefinitionReactiveTransaction 对象,你可以启动事务、回滚和提交。以下示例展示了如何执行此操作:

java
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
// 显式设置事务名称是只能以编程方式完成的事情
def.setName("SomeTxName");
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

Mono<ReactiveTransaction> reactiveTx = txManager.getReactiveTransaction(def);

reactiveTx.flatMap(status -> {

	Mono<Object> tx = ...; // 将你的业务逻辑放在这里

	return tx.then(txManager.commit(status))
			.onErrorResume(ex -> txManager.rollback(status).then(Mono.error(ex)));
});
kotlin
val def = DefaultTransactionDefinition()
// 显式设置事务名称是只能以编程方式完成的事情
def.setName("SomeTxName")
def.propagationBehavior = TransactionDefinition.PROPAGATION_REQUIRED

val reactiveTx = txManager.getReactiveTransaction(def)
reactiveTx.flatMap { status ->

	val tx = ... // 将你的业务逻辑放在这里

	tx.then(txManager.commit(status))
			.onErrorResume { ex -> txManager.rollback(status).then(Mono.error(ex)) }
}

补充教学

1. 为什么大多数人更喜欢声明式事务?

虽然 TransactionTemplate 很好,但编程式事务有两个明显的缺点:

  1. 侵入性:你的业务代码(Service 类)必须引用 Spring 的事务 API (TransactionTemplate)。如果哪天你想迁移到非 Spring 框架,这部分代码得重写。相比之下,@Transactional 只是一个注解,业务逻辑保持纯净(POJO)。
  2. 代码噪音:即使回调模式简化了 try-catch-rollback,但满屏幕的 execute(new Callback... 依然影响可读性。

2. 编程式事务的“杀手锏”场景

既然声明式这么好,为什么还要学编程式?因为在某些极端精细控制的场景下,声明式无能为力:

  • 长事务优化:假设一个方法需要运行 10 秒,其中前 9 秒是复杂的计算或调用外部 API(不需要数据库事务),只有最后 1 秒是存库。
    • 声明式@Transactional 放在方法上,事务开启 10 秒,数据库连接被占用 10 秒。浪费资源!
    • 编程式:先计算 9 秒,然后用 transactionTemplate.execute(...) 包裹最后 1 秒的入库操作。性能提升 10 倍。
  • 多事务手动编排:你可能需要在一个方法里,先提交一个事务,发个消息,再开启另一个事务。声明式很难在一个方法内实现“提交后重开”。

3. 线程安全与单例

文档中提到 TransactionTemplate线程安全的。 这意味着你完全可以在 Spring 配置中定义一个全局的 TransactionTemplate Bean(比如配置了特定的超时时间),然后注入到成百上千个 Service 中共用。

xml
<bean id="longTimeoutTemplate" class="org...TransactionTemplate">
    <property name="timeout" value="60"/>
</bean>

这是非常高效的做法,无需在每个 Service 里 new 它。

4. 响应式编程的陷阱

ReactiveTransactionManager 部分,你可能注意到了代码比命令式复杂得多。 reactiveTx.flatMap(status -> ...) 这是因为在响应式编程中,Context(上下文) 的传递不是基于 ThreadLocal 的。在命令式编程中,TransactionManager 知道当前线程的事务是谁;但在响应式中,必须显式地将 status 对象通过 Lambda 链传递下去,或者依赖 Reactor 的 Context API。这就是为什么 TransactionalOperator(它封装了 Context 操作)比直接用 Manager 好用得多的原因。

Based on Spring Framework.