Insight Spring Test事务自动回滚的实现

本文深入探讨了在测试环境中使用事务控制的重要性,通过Spring的@Transactional注解实现测试用例的回滚,确保数据不被持久化,从而允许测试用例重复执行。讲解了Spring如何通过TestExecutionListener实现事务的自动回滚,并介绍了相关注解的使用,如@Rollback和@NotTransactional。理解这些原理能帮助开发者更高效地编写测试用例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

测试环境事务控制的意义

  • 避免测试用例的数据持久化的影响。需要事务控制回滚,还原场景,这样测试用例可以重复使用。(tests changes to the state may affect future tests)
  • 我们的业务逻辑需要事务控制,测试环境也要同样的模拟。

测试环境-事务控制demo

/**
 * 只需要测试类添加@Transactional, 保证所有的测试方式都有事务控制,并且执行完成后自动回滚。
 * 参考 https://docs.spring.io/spring-framework/docs/4.3.13.RELEASE/spring-framework-reference/htmlsingle/#testcontext-tx-enabling-transactions
 */
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = TestConfig.class)
@Transactional
public class HibernateUserRepositoryTests {
    
    @Test
    public void createUser() {
        // track initial state in test database:
        final int count = countRowsInTable("user");

        User user = new User(...);
        repository.save(user);

        // Manual flush is required to avoid false positive in test
        sessionFactory.getCurrentSession().flush();
        assertNumUsers(count + 1);
        // 方法执行完成后,insert user 会自动回滚,不会在数据库中添加脏数据
    }
    
}

源码实现

自动回滚的原理

Spring 采用容器注入的方式,在Junit 测试框架的基础上,引入TestExecutionListener 的概念,方便拓展自定义的事件。

事务控制就是基于这样的设计,通过实现TestExecutionListener 接口,达到事务特性增强的目的

/**
 * 支持Spring Test 事务控制实现的TestExecutionListener
 * 
 * @see org.springframework.test.context.transaction.TransactionalTestExecutionListener
 */
public class TransactionalTestExecutionListener extends AbstractTestExecutionListener {
    
    /**
     * 如果是以事务的形式运行,那么会在测试方法执行前,开启事务
     * 前提条件:有@Transactional 注解,并且容器中配置了TransactionManager。SpringBoot 不用考虑,使用Spring 框架的需要检查下是否有这个配置 <tx:annotation-driven transaction-manager="transactionManager"/>
     */
    public void beforeTestMethod(TestContext testContext) throws Exception {
		final Method testMethod = testContext.getTestMethod();
		// 如果是@NotTransactional,直接跳过
		if (testMethod.isAnnotationPresent(NotTransactional.class)) {
			return;
		}

        // 检测当前的方法或者类是否需要事务、是否有配置TransactionManager
		PlatformTransactionManager tm = null;
		if (transactionAttribute != null) {
			tm = getTransactionManager(testContext, transactionAttribute.getQualifier());
		}
		// 获取到tm,开启事务。按照惯例,缓存事务对象,方便afterTestMethod 进行事务继续操作。
		if (tm != null) {
			TransactionContext txContext = new TransactionContext(tm, transactionAttribute);
			runBeforeTransactionMethods(testContext);
            // 复习下开启事务的过程:Check settings、check propagation、Acquire Connection、setAutoCommit(false)、Bind the session holder to the thread
			startNewTransaction(testContext, txContext);
			this.transactionContextCache.put(testMethod, txContext);
		}
	}
    
    public void afterTestMethod(TestContext testContext) throws Exception {
		Method testMethod = testContext.getTestMethod();
		
		// If the transaction is still active...
		TransactionContext txContext = this.transactionContextCache.remove(testMethod);
		if (txContext != null && !txContext.transactionStatus.isCompleted()) {
			try {
                // rollback or commit
				endTransaction(testContext, txContext);
			}
			finally {
				runAfterTransactionMethods(testContext);
			}
		}
	}
    
}  

TestExecutionListener 注入的原理

在测试容器启动过程中,会检测当前的测试类是否有指定 TestExecutionListeners。如果没有就采用默认的TestExecutionListeners。

/**
 * Determine the default TestExecutionListener classes.
 * 默认的TestExecutionListeners:
 * ServletTestExecutionListener
 * DependencyInjectionTestExecutionListener
 * TransactionalTestExecutionListener 就是本人啦!
 * @see org.springframework.test.context.TestContextManager
 */
protected Set<Class<? extends TestExecutionListener>> getDefaultTestExecutionListenerClasses() {
	Set<Class<? extends TestExecutionListener>> defaultListenerClasses = new LinkedHashSet<>();
	for (String className : DEFAULT_TEST_EXECUTION_LISTENER_CLASS_NAMES) {
		try {
			defaultListenerClasses.add((Class<? extends TestExecutionListener>) getClass().getClassLoader().loadClass(className));
		} catch (Throwable t) {
			
		}
	}
	return defaultListenerClasses;
}

涉及到注解

  • @Transactional 启用事务控制必要注解。测试环境下,如果加在类上就全部生效,加载方法上只有方法生效。
  • @TransactionConfiguration 配置事务测试的条件,如果有多个transactionManager 实例,需要这个注解。
  • @BeforeTransaction @AfterTransaction 用于测试类的方法注解,不受事务控制
  • @NotTransactional 标识测试的方法不受事务控制,等同于上述的注解。
  • @Rollback 自定义标识测试的方法是否要回滚,用来避免默认情况下回滚的操作。

总结

  • Spring 托管一切,集成一切,包罗万象。各种集成的方案和实现都值得借鉴。
  • 了解Spring Test 事务控制的原理,可以更好的编写测试用例,提升工作效率。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值