基于Hibernate实现3表嵌套OneToMany映射报错求助
数据库表结构
Table 1: KEY
Primary Key: KEY_ID
Table 2: KEY_VERSION
Primary Key: KEY_ID (外键关联 KEY.KEY_ID) 和 VERSION
Table 3: KEY_VERSION_BLOCK
Primary Key: KEY_ID, VERSION (外键关联 KEY_VERSION.KEY_ID, KEY_VERSION.VERSION), BLOCK_TYPE
现有Hibernate映射代码
Key 实体类
public class Key implements Serializable { @EmbeddedId private KeyId keyId; @OneToMany(mappedBy = "Key", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true) private List<KeyVersion> keyVersions; // 其他代码省略 }
KeyId 嵌入式ID类
// 注:此处仅单键列,无需嵌入式ID,后续会替换,暂时忽略 @Embeddable public class KeyId implements Serializable { @Column(name = "KEY_ID", nullable = false) public Long keyId; // 其他代码省略 }
KeyVersion 实体类
public class KeyVersion implements Serializable { @EmbeddedId private KeyVersionId versionId; @MapsId("KeyId") @JoinColumns({ @JoinColumn(name = "KEY_ID", referencedColumnName = "KEY_ID") }) @ManyToOne public Key key; @OneToMany(mappedBy = "KeyVersion", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true) private List<KeyVersionBlock> keyVersionBlocks; // 其他代码省略 }
KeyVersionId 嵌入式ID类
@Embeddable public class KeyVersionId implements Serializable { @ManyToOne private Key key; @Column(name = "VERSION") private Integer version; // 其他代码省略 }
KeyVersionBlock 实体类
public class KeyVersionBlock implements Serializable { @EmbeddedId private KeyVersionBlockId keyVersionBlockId; @MapsId("keyVersionId") @JoinColumns({ @JoinColumn(name = "KEY_ID", referencedColumnName = "KEY_ID"), @JoinColumn(name = "VERSION", referencedColumnName = "VERSION") }) @ManyToOne private KeyVersion keyVersion; // 其他代码省略 }
KeyVersionBlockId 嵌入式ID类
public class KeyVersionBlockId implements Serializable { @Column(name = "BLOCK_TYPE") private Long blockType; private KeyVersionId keyVersionId; // 其他代码省略 }
报错信息
Caused by: org.hibernate.AnnotationException:
referencedColumnNames(KEY_ID, VERSION) of
com.akamai.portal.dexp.kms.core.model.KeyVersionBlock.keyVersion
referencing com.akamai.portal.dexp.kms.core.model.KeyVersion not
mapped to a single property
at org.hibernate.cfg.BinderHelper.createSyntheticPropertyReference(BinderHelper.java:203)
~[hibernate-core-5.6.15.Final.jar:5.6.15.Final]
at org.hibernate.cfg.ToOneFkSecondPass.doSecondPass(ToOneFkSecondPass.java:104)
~[hibernate-core-5.6.15.Final.jar:5.6.15.Final]
at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.processEndOfQueue(InFlightMetadataCollectorImpl.java:1750)
~[hibernate-core-5.6.15.Final.jar:5.6.15.Final]
用户已尝试操作
曾在KEY_VERSION实体类中添加@Column(name = "KEY_ID", nullable = false, updatable = false, insertable = false) private Long keyId;,但问题未解决。
报错核心原因是嵌入式ID的映射逻辑不匹配,Hibernate无法解析多列外键到目标实体的关联关系。以下是修正后的完整映射方案:
1. 修正KeyVersionId的映射
嵌入式ID类中不能直接使用@ManyToOne,需改为基础字段映射:
@Embeddable public class KeyVersionId implements Serializable { @Column(name = "KEY_ID") private Long keyId; @Column(name = "VERSION") private Integer version; // 必须重写equals和hashCode方法 @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; KeyVersionId that = (KeyVersionId) o; return Objects.equals(keyId, that.keyId) && Objects.equals(version, that.version); } @Override public int hashCode() { return Objects.hash(keyId, version); } }
2. 修正KeyVersion实体类
调整@MapsId参数匹配KeyVersionId字段名,同时修正mappedBy的属性名(需与子实体属性名大小写一致):
public class KeyVersion implements Serializable { @EmbeddedId private KeyVersionId versionId; @MapsId("keyId") // 对应KeyVersionId中的keyId字段 @JoinColumn(name = "KEY_ID", referencedColumnName = "KEY_ID") @ManyToOne(fetch = FetchType.LAZY) private Key key; @OneToMany(mappedBy = "keyVersion", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true) private List<KeyVersionBlock> keyVersionBlocks; // 其他代码省略 }
3. 修正KeyVersionBlockId的映射
将嵌入式ID中的关联改为基础字段组合:
@Embeddable public class KeyVersionBlockId implements Serializable { @Column(name = "KEY_ID") private Long keyId; @Column(name = "VERSION") private Integer version; @Column(name = "BLOCK_TYPE") private Long blockType; // 必须重写equals和hashCode方法 @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; KeyVersionBlockId that = (KeyVersionBlockId) o; return Objects.equals(keyId, that.keyId) && Objects.equals(version, that.version) && Objects.equals(blockType, that.blockType); } @Override public int hashCode() { return Objects.hash(keyId, version, blockType); } }
4. 修正KeyVersionBlock实体类
通过@MapsId分别关联KeyVersionBlockId中的字段,添加insertable=false, updatable=false避免列映射冲突:
public class KeyVersionBlock implements Serializable { @EmbeddedId private KeyVersionBlockId keyVersionBlockId; @MapsId("keyId") // 对应KeyVersionBlockId中的keyId @JoinColumn(name = "KEY_ID", referencedColumnName = "KEY_ID", insertable = false, updatable = false) @ManyToOne(fetch = FetchType.LAZY) private Key key; @MapsId("version") // 对应KeyVersionBlockId中的version @JoinColumn(name = "VERSION", referencedColumnName = "VERSION", insertable = false, updatable = false) @ManyToOne(fetch = FetchType.LAZY) private KeyVersion keyVersion; // 其他代码省略 }
5. 修正Key实体类的mappedBy参数
确保mappedBy的值与KeyVersion中的属性名一致(小写key):
public class Key implements Serializable { @EmbeddedId private KeyId keyId; @OneToMany(mappedBy = "key", fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true) private List<KeyVersion> keyVersions; // 其他代码省略 }
额外说明
- 所有嵌入式ID类必须重写
equals和hashCode方法,否则Hibernate无法正确识别实体唯一性。 - 关联字段添加
insertable = false, updatable = false,避免重复映射同一列导致的冲突。 - 若后续替换
Key的嵌入式ID为普通主键,只需将@EmbeddedId改为@Id并删除KeyId类即可,不影响其他关联逻辑。
内容的提问来源于stack exchange,提问作者Archana

