Spring Boot H2测试中如何仅执行一次data.sql解决主键冲突
解决H2内存库测试时data.sql重复执行导致的唯一约束冲突问题
这个问题我之前帮好几个开发者排查过,核心矛盾就是Spring Boot在测试场景下默认会为每个测试类/方法重新初始化应用上下文,再加上data.sql默认会在每次上下文启动时自动执行,自然就会重复插入数据触发唯一约束了。给你几个实用的解决思路,按需选择:
1. 复用Spring Boot测试上下文,让它只加载一次
默认情况下JUnit 5会为每个测试方法创建新的测试实例,导致Spring上下文重复初始化。你可以通过以下方式让上下文复用:
- 在测试类上添加
@TestInstance(TestInstance.Lifecycle.PER_CLASS),这样同一个测试类里的所有方法共享一个上下文实例,data.sql只会执行一次。 - 如果是多个测试类需要共享上下文,确保它们的配置完全一致(比如都用
@SpringBootTest且没有自定义的@ContextConfiguration),Spring会自动缓存并复用上下文,避免重复初始化。
2. 控制data.sql的执行时机,避免重复执行
Spring Boot提供了配置项来控制初始化脚本的执行策略,在application-test.yml里添加:
spring.sql.init.mode: embedded
这个配置会让初始化脚本仅在嵌入式数据库(比如你的H2)第一次启动时执行,后续即使上下文重启(如果没有复用的话),只要数据库实例还存在就不会重复跑脚本。如果想完全禁用自动执行,也可以把值设为never,然后手动控制脚本执行。
3. 改用@Sql注解精确控制脚本执行
放弃data.sql的自动执行,改用Spring的@Sql注解来指定脚本执行时机,这样能完全掌控何时插入数据:
- 如果希望脚本在整个测试类启动前执行一次:
@SpringBootTest @Sql(scripts = "/data.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_CLASS) public class YourTestClass { // 测试方法 }
- 如果每个测试方法都需要干净的初始数据,可以结合事务回滚:
@SpringBootTest @Transactional @Rollback @Sql(scripts = "/data.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) public class YourTestClass { // 每个测试方法执行前都会插入数据,执行后自动回滚,避免数据残留 }
4. 调整Hibernate的DDL配置,每次测试都重建表
把spring.jpa.hibernate.ddl-auto从update改成create-drop,在application-test.yml里设置:
spring.jpa.hibernate.ddl-auto: create-drop
这样每次上下文启动时,Hibernate会先销毁旧表再创建新表,data.sql插入数据时表是完全干净的,自然不会有唯一约束冲突。这个方案适合每个测试都需要独立干净环境的场景,缺点是测试启动速度会稍慢一点。
内容的提问来源于stack exchange,提问作者Luke
相关产品推荐
相关产品推荐

