JPA一对一級聯時IdentifierGenerationException的成因與解決
问题:一对一双向关联共享ID时,新建未持久化父实体无法创建子实体
场景背景
- 要实现
OverwriteNewbuildingAllocationValues(简称ONV)与Measure的一对一双向关联,要求级联所有操作,二者共享ID Measure的ID由数据库自动生成(UUID类型),受遗留系统限制不能修改Measure表,因此新建ONV表,其主键作为MeasureID的外键
实体代码片段
Measure实体
@Id @GeneratedValue(strategy = GenerationType.AUTO) @Column(name = "MES001_ID", columnDefinition = "uniqueidentifier") private UUID id; // 存在问题的一对一关联 @OneToOne(mappedBy = "measure", cascade = CascadeType.ALL, orphanRemoval = true) private OverwriteNewbuildingAllocationValues overwriteNewbuildingAllocationValues; // 正常工作的一对多关联 @OneToMany(mappedBy = "measure", cascade = CascadeType.ALL, orphanRemoval = true) private List<MeasureAccruals> measureAccruals;
OverwriteNewbuildingAllocationValues实体
@Id private UUID measureId; @OneToOne(fetch = FetchType.LAZY) @MapsId @JoinColumn(name = "ONV001_MES001_ID") private Measure measure;
子实体创建逻辑
public void createOverwriteNewbuildingAllocationValue() { if (selectedMeasure != null && selectedMeasure.getOverwriteNewbuildingAllocationValues() == null) { OverwriteNewbuildingAllocationValues tempONV = new OverwriteNewbuildingAllocationValues(); selectedMeasure.setOverwriteNewbuildingAllocationValues(tempONV); tempONV.setMeasure(selectedMeasure); } }
错误表现
调用entityManager.merge(selectedMeasure)保存新建的未持久化Measure时,抛出异常:
org.hibernate.id.IdentifierGenerationException: attempted to assign id from null one-to-one property [...OverwriteNewbuildingAllocationValues.measure]
其他现象:
- 子实体构造函数被调用两次,
@PrePersist方法中measure属性为null(明明之前已手动设置关联) - 已持久化的
Measure关联ONV时,读、改、删操作均正常 - 同配置的一对多关联(
MeasureAccruals)工作无问题 - 使用版本:hibernate-jpa-2.1-api-1.0.2-Final
可行解决方案
方案1:调整关联的维护端
当前Measure是关联的被维护端(mappedBy="measure"),级联操作由ONV触发,改成让Measure作为维护端,就能触发从父到子的级联创建:
修改后的Measure关联注解:
@OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY) @JoinColumn(name = "MES001_ID", referencedColumnName = "ONV001_MES001_ID", insertable = false, updatable = false) private OverwriteNewbuildingAllocationValues overwriteNewbuildingAllocationValues;
修改后的ONV关联注解:
@Id @Column(name = "ONV001_MES001_ID") private UUID measureId; @OneToOne(fetch = FetchType.LAZY) @MapsId @JoinColumn(name = "ONV001_MES001_ID") private Measure measure;
方案2:先持久化父实体,再关联子实体
如果不想修改实体映射,调整创建流程:
- 先调用
entityManager.persist(selectedMeasure)将Measure持久化(生成ID) - 再调用
createOverwriteNewbuildingAllocationValue()创建并关联ONV - 最后调用
entityManager.merge(selectedMeasure)保存关联关系
方案3:手动同步ID时序
在ONV实体中添加@PrePersist方法,手动同步ID:
@PrePersist public void prePersist() { if (measure != null) { this.measureId = measure.getId(); } }
同时修改创建逻辑,确保先获取父实体的ID:
public void createOverwriteNewbuildingAllocationValue() { if (selectedMeasure != null && selectedMeasure.getOverwriteNewbuildingAllocationValues() == null) { // 若Measure未持久化,先持久化并刷新获取ID if (selectedMeasure.getId() == null) { entityManager.persist(selectedMeasure); entityManager.flush(); entityManager.refresh(selectedMeasure); } OverwriteNewbuildingAllocationValues tempONV = new OverwriteNewbuildingAllocationValues(); tempONV.setMeasure(selectedMeasure); selectedMeasure.setOverwriteNewbuildingAllocationValues(tempONV); } }
内容的提问来源于stack exchange,提问作者Hypernia
相关产品推荐
相关产品推荐

