更新现有父实体时Hibernate不创建新子实体的问题排查
场景复现
我当前维护的项目中有一对JPA实体的一对多关联关系,实体代码如下:
Parent 实体
@Entity @Table(name = "PARENT_TABLE") public class Parent { @Id @GeneratedValue(generator = "uuid2") @GenericGenerator(name = "uuid2", strategy = "uuid2") @Column(name = "PARENT_ID", updatable = false, nullable = false, length = 16) @Type(type="uuid-binary") private UUID id; private String parentName; @OneToMany(fetch=FetchType.LAZY, mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true) @JoinColumn(name="PARENT_ID") private List<Child> children; }
Child 实体
@Entity @Table(name = "CHILD_TABLE") public class Child { @Id @GeneratedValue(generator = "uuid2") @GenericGenerator(name = "uuid2", strategy = "uuid2") @Column(name = "CHILD_ID", updatable = false, nullable = false, length = 16) @Type(type="uuid-binary") private UUID id; private String childName; @ManyToOne(fetch=FetchType.LAZY) @JoinColumn(name = "PARENT_ID", referencedColumnName = "PARENT_ID") private Parent parent; }
现有一个已包含2个子实体的父实体,我尝试通过更新父实体的方式添加新子实体,执行代码如下:
List<Child> existingChild = getFromDbByParent(parent); existingChild.add(new Child()); hibernateTemplate.update(parent); // 也试过hibernateTemplate.saveOrUpdate(parent);
但执行失败,抛出如下错误:
2019-12-19 13:02:04.589 DEBUG SqlExceptionHelper io-12347-exec-1 could not execute batch [update CHILD_TABLE set CHILD_ID =null where PARENT_ID=?] java.sql.BatchUpdateException: ORA-01407: cannot update ("DB"."CHILD_TABLE"."CHILD_ID") to NULL
新增父实体及关联子实体的操作可正常执行,JPA级联注解也生效。目前项目正处于仓库层向Hibernate迁移的阶段,我希望尽量避免修改业务逻辑,已知单独保存子实体的方案更优,但想找到适配现有写法的解决办法。
问题原因
问题出在Parent实体中@OneToMany注解的配置冲突:你同时使用了mappedBy = "parent"和@JoinColumn(name="PARENT_ID")。
mappedBy表示当前是关联关系的被动端,关联的维护权交给Child实体中的parent字段;- 但额外添加的
@JoinColumn会让Hibernate错误地将这个@OneToMany识别为主动端(类似单向关联),导致Hibernate在处理新增子实体时,尝试先更新子表的CHILD_ID为null,再插入新记录——而CHILD_ID作为主键不允许为null,因此抛出ORA-01407错误。
解决方案
只需要修改Parent实体的关联配置,移除多余的@JoinColumn,保留mappedBy即可,因为关联关系已经由Child的parent字段负责维护:
修改后的Parent实体关联部分:
@OneToMany(fetch=FetchType.LAZY, mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true) // 移除@JoinColumn(name="PARENT_ID") private List<Child> children;
另外还有一个关键细节:添加新Child时,必须手动设置它与父实体的关联,否则Hibernate无法正确维护外键关系:
List<Child> existingChild = getFromDbByParent(parent); Child newChild = new Child(); newChild.setParent(parent); // 必须设置关联 existingChild.add(newChild); hibernateTemplate.update(parent);
这样修改后,Hibernate会正确识别关联关系的维护端,级联新增子实体时会自动为新Child生成UUID主键,不会再出现更新主键为null的错误。
内容的提问来源于stack exchange,提问作者Vivek Vardhan

