Spring Boot+Hibernate+Postgres非自增ID实体级联保存报错求助
Spring Boot+Hibernate手动指定ID级联保存报错解决方案
问题根因
CrudRepository的save方法底层会先判断实体是否为新实例,默认判断逻辑为:
- 若实体ID为
null,判定为新实例,执行persist操作,触发CascadeType.PERSIST级联规则 - 若实体ID有值,判定为已存在实例,执行
merge操作,先查询数据库对应记录,查询不存在就抛出EntityNotFoundException
你手动给所有实体设置了UUID值,Hibernate判定为已存在实例执行merge,而你配置的级联规则仅为CascadeType.PERSIST,merge操作不会触发级联保存关联的SourceCompany、SourceUser,导致查询关联实体时找不到记录报错。
这也是自动生成ID时逻辑正常的原因:自动生成ID的新实体初始ID为null,触发persist操作,级联逻辑正常执行。
通用解决方案(适配复杂实体结构)
推荐所有实体实现Persistable<UUID>接口,手动控制新实体判定逻辑,完全兼容手动指定ID、自动生成ID两种场景。
改造步骤
- 每个实体类实现
Persistable<UUID>接口 - 新增transient类型的
isNew标记字段,默认值为true - 新增生命周期回调方法,实体持久化/加载后将
isNew标记为false
代码示例
以SourceCompany改造为例,其余SourceUser、SourceInfo实体按相同规则改造即可:
@Data @Entity @EqualsAndHashCode(onlyExplicitlyIncluded = true) @Table(name = "source_company") public class SourceCompany implements Persistable<UUID> { @Id @EqualsAndHashCode.Include private UUID id; private String name; @OneToMany(cascade = CascadeType.PERSIST, mappedBy = "company") private Set<SourceUser> users; // transient字段不会被持久化到数据库 @Transient private boolean isNew = true; @Override public UUID getId() { return this.id; } @Override public boolean isNew() { return this.isNew; } // 实体持久化前、从数据库加载后,标记为非新实体 @PrePersist @PostLoad public void markNotNew() { this.isNew = false; } }
改造完成后原有业务代码无需修改,即可正常执行级联保存。
其他可选方案
- 修改级联策略:将关联字段的级联规则改为
CascadeType.MERGE或CascadeType.ALL,但该方案存在副作用,会触发关联实体的更新逻辑,不适用于仅需要级联新增的场景 - 手动保存关联实体:业务代码中先依次保存SourceCompany、SourceUser,最后保存SourceInfo,该方案不需要修改实体定义,但复杂关联结构下编码成本高,容易漏存关联实体
内容的提问来源于stack exchange,提问作者Davide
相关产品推荐
相关产品推荐

