使用data.sql导入数据后Spring Boot新增数据报主键重复错误
兄弟,你遇到的这个主键重复问题,核心原因其实很明确:你通过data.sql手动插入带ID的数据后,PostgreSQL对应serial类型的序列当前值还停留在初始状态,Hibernate生成新ID的时候还是从1开始,自然就和已有的数据ID撞车了。
先说说你之前尝试的方案为什么没起作用:
- 第一种方案(重启改配置):只是停止了初始化脚本的执行,但数据库里的序列值没更新,Hibernate还是会用旧的序列值生成ID,冲突依然存在。
- 第二种方案(改成
GenerationType.TABLE):TABLE策略是用单独的表存主键生成值,你没初始化这张表的起始值,它还是从1开始,照样和已有的ID冲突。
下面给你几个靠谱的解决方案,按需选择:
方案一:在data.sql末尾更新序列值
PostgreSQL会给serial类型的字段自动生成一个序列,默认命名是表名_字段名_seq,你的joke表ID对应的序列就是joke_id_seq。在data.sql的最后加上这句SQL,插入完数据后把序列的当前值设为表中最大的ID:
-- 同步序列值到表中最大ID SELECT setval('joke_id_seq', (SELECT MAX(id) FROM joke));
这样后续Hibernate用序列生成ID时,就会从这个最大值的下一个数字开始,不会再出现重复。
方案二:调整主键生成策略适配PostgreSQL
你现在用了@Column(columnDefinition = "serial"),PostgreSQL的serial本质是自增列,对应的Hibernate生成策略应该是GenerationType.IDENTITY,而不是AUTO。修改Joke实体的主键注解:
@Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(columnDefinition = "serial") private Long id;
IDENTITY策略会让Hibernate直接依赖数据库的自增机制,不再自己维护序列值,这样不管你用data.sql插入多少数据,新增时都会自动用数据库的自增ID,完美避免冲突。
方案三:如果用Flyway,把数据初始化放到迁移脚本里
看你的数据库名是flyway_demo,估计你在用Flyway做数据库迁移。把初始化数据放到Flyway的版本迁移脚本里(比如V1__init_jokes.sql),然后在脚本末尾加上方案一里的序列更新语句,Flyway执行时会自动完成数据插入和序列同步,一步到位。
额外注意点
- 你的
JokeRepository里的findById(long id)参数类型有问题:JpaRepository的泛型是<Joke, Integer>,但你的ID是Long类型,要么把方法改成Joke findById(Long id);,要么把泛型改成<Joke, Long>,避免后续出现类型转换异常。 - 如果你不需要每次启动都重建表,建议把
spring.jpa.hibernate.ddl-auto从create改成update或者none,不然每次启动都会清空表再重新插入数据,影响测试效率。
内容的提问来源于stack exchange,提问作者SirBepy

