Spring Boot集成测试:如何让@BeforeEach先于@Sql执行?
1. 自定义TestExecutionListener控制执行顺序
Spring Test的@Sql由SqlScriptsTestExecutionListener负责执行,要让表清理逻辑跑在@Sql之前,最可靠的方式是自定义一个TestExecutionListener,并让它优先执行:
首先编写自定义监听器类,实现TestExecutionListener接口,在beforeTestMethod方法里调用你的JDBC清理逻辑:
public class TableCleanupListener implements TestExecutionListener { @Autowired private JdbcTemplate jdbcTemplate; @Override public void beforeTestMethod(TestContext testContext) throws Exception { // 手动注入上下文Bean(避免自动注入失效) if (jdbcTemplate == null) { jdbcTemplate = testContext.getApplicationContext().getBean(JdbcTemplate.class); } // 执行全表截断逻辑 truncateAllTables(jdbcTemplate); } private void truncateAllTables(JdbcTemplate jdbcTemplate) { // 这里放入你已实现的清理代码 } }
然后在测试类上配置监听器,注意要合并默认监听器,否则@Sql的执行逻辑会失效:
@SpringBootTest @TestExecutionListeners( listeners = TableCleanupListener.class, mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS ) public class RepositoryTestWithJDBC { // 你的测试用例 }
监听器按注册顺序执行,自定义的TableCleanupListener会先于默认的SqlScriptsTestExecutionListener运行,完美解决顺序问题。
2. 利用JUnit 5的@BeforeEach+@Order(需调整@Sql执行阶段)
如果想用JUnit的生命周期注解,需要注意@Sql默认在@BeforeEach之前执行,所以得修改@Sql的executionPhase,让它在清理之后运行:
@SpringBootTest public class RepositoryTestWithJDBC { @Autowired private JdbcTemplate jdbcTemplate; @BeforeEach @Order(1) void cleanTables() { truncateAllTables(jdbcTemplate); } @Sql( scripts = "classpath:test-data.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD ) @Test void testCase1() { // 测试逻辑 } }
通过@Order指定清理方法优先执行,再配合@Sql的显式执行阶段配置,能实现先清理再加载测试数据的顺序。不过这种方式依赖JUnit的顺序规则,稳定性不如监听器方案。
3. Testcontainers结合初始化/前置脚本
既然用了Testcontainers,也可以直接在容器层面处理清理:
- 要么在容器启动时加载包含存储过程的初始化脚本,解决分隔符问题可以在脚本开头加
DELIMITER //,结尾改回DELIMITER ;; - 要么在每个测试前通过容器的JDBC连接执行清理逻辑,比如直接调用存储过程:
@Testcontainers @SpringBootTest public class RepositoryTestWithJDBC { @Container static MySQLContainer<?> mysqlContainer = new MySQLContainer<>("mysql:8.0") .withInitScript("init-procedure.sql"); @Autowired private JdbcTemplate jdbcTemplate; @BeforeEach void callCleanupProcedure() { jdbcTemplate.execute("CALL clean_all_tables()"); } }
存储过程脚本示例(解决分隔符问题):
DELIMITER // CREATE PROCEDURE clean_all_tables() BEGIN -- 存储过程具体清理逻辑 END // DELIMITER ;
4. 事务回滚方案(无需手动清理)
这是最省心的方案:给测试类加@Transactional和@Rollback,让每个测试用例跑在独立事务里,测试结束自动回滚所有操作,不需要手动截断表:
@SpringBootTest @Transactional @Rollback public class RepositoryTestWithJDBC { @Sql(scripts = "classpath:test-data.sql") @Test void testCase1() { // 测试逻辑 } }
注意:如果测试中涉及非事务操作(比如调用外部服务、手动提交事务),回滚会失效。另外@Sql默认在事务开启前执行,插入的测试数据会被包含在事务里,测试结束一起回滚,符合需求。
补充:解决存储过程的@Sql分隔符问题
你之前遇到的存储过程执行报错,可通过@SqlConfig指定分隔符解决:
@Sql( scripts = "classpath:cleanup-procedure.sql", config = @SqlConfig(separator = "//") )
把脚本里的存储过程用//作为结束符,Spring就能正确解析,不用再纠结默认的分号分隔问题。
内容的提问来源于stack exchange,提问作者ishan

