使用H2脚本初始化Spring测试环境时插入SQL数据报主键冲突
场景还原
将MySQL初始化脚本迁移到H2内存数据库用于Spring测试,相关配置与脚本如下:
src/test/resources/application.yaml配置:
spring: datasource: url: jdbc:h2:mem:bbstatstest username: sa password: sa driverClassName: org.h2.Driver jpa: hibernate: ddl-auto: none naming: physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl open-in-view: false properties: hibernate: format_sql: true show-sql: true sql: init: mode: always
- Spring测试类代码:
@SpringBootTest @Sql({"/schema.sql", "/data.sql"}) // 文件存放于src/test/resources目录 class BasketballStatsSpringApplicationTests { @Test void contextLoads() { } }
schema.sql表结构脚本:
CREATE SCHEMA IF NOT EXISTS bbstatstest; CREATE TABLE IF NOT EXISTS bbstatstest.GeoContexts ( id INT NOT NULL, parent_id INT NULL DEFAULT NULL, name VARCHAR(50) NOT NULL, type ENUM('CONTINENT', 'COUNTRY', 'REGION', 'STATE', 'DISTRICT') NULL DEFAULT NULL, PRIMARY KEY (id), CONSTRAINT geocontexts_self_fk FOREIGN KEY (parent_id) REFERENCES bbstatstest.GeoContexts (id) ON DELETE NO ACTION ON UPDATE CASCADE);
data.sql数据插入脚本:
INSERT INTO bbstatstest.GeoContexts (id, parent_id, name) VALUES (1, NULL, 'Africa'), (2, NULL, 'Antarctica'), (3, NULL, 'Asia'), (4, NULL, 'Europe'), (5, NULL, 'North America'), (6, NULL, 'Oceania'), (7, NULL, 'South America');
执行测试后抛出主键冲突异常:
Caused by: org.h2.jdbc.JdbcSQLIntegrityConstraintViolationException: Unique index or primary key violation: "PRIMARY KEY ON BBSTATSTEST.GEOCONTEXTS(ID) ( /* key:1 */ 1, NULL, 'Africa', NULL)"; SQL statement:
INSERT INTO bbstatstest.GeoContexts (id, parent_id, name) VALUES (1, NULL, 'Africa'), (2, NULL, 'Antarctica'), (3, NULL, 'Asia'), (4, NULL, 'Europe'), (5, NULL, 'North America'), (6, NULL, 'Oceania'), (7, NULL, 'South America') [23505-224]
问题原因
脚本被重复执行了两次:
spring.sql.init.mode=always配置会触发Spring Boot自动执行src/test/resources下的schema.sql和data.sql,完成数据库初始化。- 测试类上的
@Sql注解又手动指定执行同一批脚本,导致数据插入操作被执行两次,主键1-7重复插入触发唯一约束异常。
解决方法
任选以下一种方案即可:
方案1:移除测试类上的@Sql注解
保留spring.sql.init.mode=always配置,让Spring Boot自动完成初始化,测试类简化为:
@SpringBootTest class BasketballStatsSpringApplicationTests { @Test void contextLoads() { } }
方案2:修改spring.sql.init.mode配置
将application.yaml中的spring.sql.init.mode改为never,禁用自动初始化,完全通过@Sql注解控制脚本执行:
spring: # 其他配置保持不变 sql: init: mode: never
额外兼容建议(非当前报错原因)
MySQL的ENUM类型在H2中默认支持,但为了避免后续类型兼容问题,建议在H2的JDBC URL中添加MySQL兼容模式:
spring: datasource: url: jdbc:h2:mem:bbstatstest;MODE=MySQL;DATABASE_TO_LOWER=TRUE
内容的提问来源于stack exchange,提问作者Kawu

