使用Hibernate/Spring Boot测试HSQL数据库时遇唯一约束违反问题
嘿,看起来你在Hibernate/Spring Boot和HSQL的实体关联上踩了个典型的坑,我来帮你拆解问题根源和解决办法:
你遇到的integrity constraint violation: unique constraint or index violation; SYS_PK_10126 table: USER异常,主要是由两个关键配置错误引发的:
1. 级联操作触发User重复保存
你的Shoebox实体里,@ManyToOne注解设置了cascade = CascadeType.ALL:
@ManyToOne(cascade = CascadeType.ALL) @JoinColumn(name="OwnerID") @JsonBackReference(value="shoebox_owner") User owner;
CascadeType.ALL包含了PERSIST(保存)操作,这意味着当你调用shoeboxes.save(sb)时,Hibernate会自动尝试级联保存关联的User对象。但你在测试代码里已经提前通过users.save(user)把ID=1的User存入数据库了,此时Hibernate再尝试保存同一个User,就会触发数据库的主键唯一约束冲突——毕竟数据库里已经有这条记录了。
2. 双向关联映射缺失导致多余中间表(附带脚本错误)
在User实体的@OneToMany注解中,你没有指定mappedBy属性:
@OneToMany(fetch = FetchType.LAZY) @JsonManagedReference(value="shoebox_owner") private List<Shoebox> shoeboxes;
mappedBy是双向关联的关键,用来指定关系的维护端(这里应该是Shoebox的owner字段)。缺失这个配置会让Hibernate误以为这是个单向关联,自动生成多余的USER_SHOEBOX中间表。更糟的是,你的数据库脚本里这个中间表的外键还写错了:
CONSTRAINT FKIR9SOKRCOQ33LCQTNR0LDXO93 FOREIGN KEY(USER_ID) REFERENCES PUBLIC.SHOEBOXUSER(ID)
这里应该指向PUBLIC.USER(ID),而不是不存在的SHOEBOXUSER表——虽然这个错误不是当前异常的直接原因,但后续肯定会引发关联操作失败。
第一步:调整级联策略
把Shoebox的@ManyToOne级联类型改成更合理的选项,比如直接去掉CascadeType.ALL(因为你已经提前保存了User):
@ManyToOne @JoinColumn(name="OwnerID") @JsonBackReference(value="shoebox_owner") User owner;
如果确实需要级联操作(比如更新User时同步更新关联的Shoebox),可以只保留必要的级联类型:
@ManyToOne(cascade = {CascadeType.MERGE, CascadeType.REFRESH}) @JoinColumn(name="OwnerID") @JsonBackReference(value="shoebox_owner") User owner;
第二步:修复双向关联映射
在User的@OneToMany注解中添加mappedBy="owner",明确关系由Shoebox的owner字段维护:
@OneToMany(fetch = FetchType.LAZY, mappedBy = "owner") @JsonManagedReference(value="shoebox_owner") private List<Shoebox> shoeboxes;
这样Hibernate就不会再生成多余的USER_SHOEBOX表,直接用Shoebox的OwnerID作为外键关联。
第三步:修正数据库脚本错误
如果你保留了中间表(其实修复关联后不需要了),把外键指向改成正确的User表:
CONSTRAINT FKIR9SOKRCOQ33LCQTNR0LDXO93 FOREIGN KEY(USER_ID) REFERENCES PUBLIC.USER(ID)
或者直接删除USER_SHOEBOX表的创建语句,因为双向关联修复后不需要这个中间表了。
- 尽量避免手动设置主键值(比如你直接给User设ID=1),可以用
@GeneratedValue让Hibernate自动生成主键,减少主键冲突风险:
@Id @Column(name = "ID") @GeneratedValue(strategy = GenerationType.IDENTITY) private long ID;
- 测试时开启Hibernate的SQL日志,能直观看到执行的SQL语句,更容易定位这类持久化问题。
内容的提问来源于stack exchange,提问作者Ziz

