Spring Boot集成测试数据库初始化及事务冲突问题求助
你遇到的问题是典型的测试事务与HTTP请求事务冲突,加上手动管理事务后触发的连接池资源释放问题。下面给你几个优雅的解决方案,按推荐程度排序:
1. 用@BeforeTransaction分离数据初始化与测试事务
这是Spring Test提供的最贴合场景的方案——把BLOB数据的初始化逻辑放到测试事务启动之前执行,这样初始化操作会直接提交到数据库,不会和测试方法的事务绑定,自然避免死锁,也不会触发连接关闭异常。
修改你的测试类:
@TestExecutionListeners({DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class, TransactionalTestExecutionListener.class, DbUnitTestExecutionListener.class}) @RunWith(SpringJUnit4ClassRunner.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @DatabaseSetup(MyTest.DATASET) @DatabaseTearDown(type = DatabaseOperation.DELETE_ALL, value = { MyTest.DATASET }) @DirtiesContext public class MyTest { protected static final String DATASET = "classpath:dbunit/customer.xml"; @Autowired private TestRestTemplate restTemplate; @Autowired private PersistenceService persistenceService; // 把初始化逻辑移到这里,在测试事务启动前执行 @BeforeTransaction public void prepareTestData() throws Exception { prepareDatabase(persistenceService); } @Test @Transactional public void afterGroupDeleteCrlShouldContainsAllCertificates() throws Exception { // 现在restTemplate的请求能看到已提交的初始化数据,不会有事务冲突 restTemplate.delete("/customer/"); // 这里可以添加你的断言逻辑 } }
原理:@BeforeTransaction标注的方法会在@Transactional测试方法的事务创建前执行,所有操作直接提交到数据库,测试方法的事务会基于这个已初始化的数据库状态运行,HTTP请求(另一个线程)也能正常读取到数据,彻底避免死锁和连接问题。
2. 用TransactionTemplate创建独立初始化事务
如果因为某些原因不能用@BeforeTransaction,可以用TransactionTemplate为数据初始化创建一个独立的、自动提交的事务,和测试方法的事务完全隔离:
// 注入TransactionTemplate @Autowired private TransactionTemplate transactionTemplate; @Test public void afterGroupDeleteCrlShouldContainsAllCertificates() throws Exception { // 用独立事务执行初始化,执行完自动提交 transactionTemplate.execute(status -> { try { prepareDatabase(persistenceService); } catch (Exception e) { status.setRollbackOnly(); throw new RuntimeException(e); } return null; }); // 此时数据已提交,restTemplate请求不会有事务冲突 restTemplate.delete("/customer/"); }
优势:灵活控制初始化事务的行为(比如设置隔离级别),同时不会影响测试方法的事务管理,也不会触发连接池的关闭异常。
3. 避免使用TestTransaction,手动用事务管理器管理事务
你之前用TestTransaction手动提交后出现连接关闭异常,是因为TestTransaction.end()会强制关闭当前事务的连接,但后续DbUnit的@DatabaseTearDown还会尝试使用连接池中的连接。改用Spring的PlatformTransactionManager手动管理事务可以避免这个问题:
@Autowired private PlatformTransactionManager transactionManager; @Test public void afterGroupDeleteCrlShouldContainsAllCertificates() throws Exception { TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition()); try { prepareDatabase(persistenceService); transactionManager.commit(status); // 手动提交初始化事务 } catch (Exception e) { transactionManager.rollback(status); throw e; } restTemplate.delete("/customer/"); }
注意:这种方式需要自己处理事务的提交和回滚,不如前两种简洁,但能精准控制事务生命周期。
内容的提问来源于stack exchange,提问作者piphonom

