You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Boot集成测试中填充测试数据时出现主键冲突问题排查

我来帮你分析下问题所在,以及对应的解决办法:

问题根源

你遇到的主键冲突,核心原因在于ID生成策略和手动插入测试数据的不兼容:

  • 你的AbstractBaseEntity中@GeneratedValue用了默认策略,Hibernate针对H2数据库会默认采用SEQUENCE(序列)生成策略,而非依赖数据库自增的IDENTITY。
  • 每次测试前,@DirtiesContext(classMode = BEFORE_EACH_TEST_METHOD)会重置Spring上下文,触发Hibernate执行ddl-auto=create重建表,然后执行data.sql手动插入了ID为1、2的记录。但手动插入ID并不会更新Hibernate维护的序列值,序列的初始值依然是1。
  • 当执行create测试的save操作时,Hibernate从序列获取下一个值(1),尝试插入ID=1的新记录,但这个ID已经被data.sql的测试数据占用,直接触发主键约束冲突。

解决方案

这里有几个可行的解决思路,你可以根据自己的需求选择:

方案1:切换到IDENTITY自增策略

修改AbstractBaseEntity的ID生成策略,让Hibernate依赖H2数据库的自增列特性,这样手动插入ID后,数据库会自动更新自增计数器的当前值,后续save操作会使用下一个可用的ID:

@MappedSuperclass
public abstract class AbstractBaseEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY) // 显式指定IDENTITY策略
    protected Long id;
    // 省略其他代码
}

方案2:手动更新序列值

如果不想修改ID生成策略,可以在data.sql末尾添加语句,手动将Hibernate默认的序列值重置为大于手动插入的最大ID:

INSERT INTO meals VALUES (1, 900000000000, 122.5, 'Soup');
INSERT INTO meals VALUES (2, 1800000000000, 155.5, 'Stake');
-- 重置Hibernate默认序列,从3开始
ALTER SEQUENCE hibernate_sequence RESTART WITH 3;

这样后续save操作获取序列值时会从3开始,不会和已有的1、2冲突。

方案3:不要手动指定ID插入测试数据

修改data.sql,去掉手动指定的ID字段,让Hibernate自动生成ID:

INSERT INTO meals (cooking_time, cost, name) VALUES (900000000000, 122.5, 'Soup');
INSERT INTO meals (cooking_time, cost, name) VALUES (1800000000000, 155.5, 'Stake');

这种方式下,测试数据的ID由Hibernate自动生成,后续save操作的ID会自然延续,不会出现冲突。

额外建议

如果希望更灵活地控制测试数据的加载,可以考虑使用Spring的@Sql注解,在需要的测试类或方法上指定加载数据脚本,替代全局的data.sql,避免全局执行带来的潜在问题:

@SpringBootTest
@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)
@Sql("classpath:test-data.sql") // 指定测试专用的数据脚本
public class RestaurantApplicationTest {
    // 测试代码...
}

内容的提问来源于stack exchange,提问作者Jose Barera

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 09:18:12