1. 引言

Spring在应用代码和集成测试中提供了强大的声明式事务管理支持。但有时我们需要对事务边界进行更精细的控制。本文将探讨如何在Spring的事务测试中,通过编程方式与自动事务进行交互

2. 前置条件

假设我们有一个包含集成测试的Spring应用,特别是需要与数据库交互的测试(例如验证持久层行为)。考虑一个标准的事务测试类:

@Transactional
@ContextConfiguration(classes = { HibernateXMLConf.class })
@ExtendWith(SpringExtension.class)
class HibernateBootstrapIntegrationTest {

在这种测试中,每个测试方法都会被包装在一个事务中,方法退出时自动回滚。当然也可以只注解特定方法,本文讨论的内容同样适用。

3. TestTransaction类

我们将重点讨论一个核心类:org.springframework.test.context.transaction.TestTransaction。这是一个工具类,提供静态方法用于在测试中操作事务。每个方法都作用于测试方法执行期间的唯一当前事务。

3.1. 检查当前事务状态

测试中常需要验证状态,因此可能需要检查是否存在活动事务:

assertTrue(TestTransaction.isActive());

或检查当前事务是否标记为回滚:

assertTrue(TestTransaction.isFlaggedForRollback());

如果标记为回滚,Spring会在事务结束前(自动或编程方式)回滚;否则会提交。

3.2. 标记事务提交或回滚

可以编程式修改事务结束前的提交/回滚策略:

TestTransaction.flagForCommit();
TestTransaction.flagForRollback();

⚠️ 注意:测试中的事务默认标记为回滚。但如果方法有@Commit注解,则默认标记为提交:

@Test
@Commit
public void testFlagForCommit() {
    assertFalse(TestTransaction.isFlaggedForRollback());
}

这些方法仅标记事务,不会立即提交或回滚,而是在事务结束前生效。

3.3. 启动和结束事务

要提交或回滚事务,可以让方法退出,或显式结束:

TestTransaction.end();

后续若需再次操作数据库,必须启动新事务:

TestTransaction.start();

✅ 新事务会根据方法默认策略(回滚或提交)重新标记,之前的flagFor…调用对新事务无效。

4. 实现细节

TestTransaction并非魔法,我们通过其实现了解Spring测试事务的内部机制。它的方法只是获取当前事务并封装部分功能。

4.1. TestTransaction如何获取当前事务?

直接看源码:

TransactionContext transactionContext
  = TransactionContextHolder.getCurrentTransactionContext();

TransactionContextHolderThreadLocal的静态包装器,持有TransactionContext实例。

4.2. 谁设置线程本地上下文?

追踪setCurrentTransactionContext调用者,发现唯一调用者是TransactionalTestExecutionListener.beforeTestMethod。该监听器是Spring为@Transactional测试自动配置的。

⚠️ 注意:TransactionContext不持有实际事务引用,而是PlatformTransactionManager的门面。Spring核心代码虽抽象分层,但底层并无黑魔法——只是大量必要的簿记、管道和异常处理。

5. 结论

本文介绍了在Spring测试中编程式操作事务的方法。所有示例代码可在GitHub项目中找到(Maven项目,可直接导入运行)。


原始标题:Programmatic Transactions in the TestContext Framework