Hibernate 6升级后JPA多级OneToMany关联IdClass映射报错求助
Hibernate 6升级后复合主键关联报错问题
问题背景
团队从Hibernate 5.6升级到Hibernate 6后,部分JPA实体因新增校验规则无法正常启动,以下是简化后的场景:
表结构
- Adapter表:ADID(Integer类型主键,对应实体
adapterId)、name(String类型) - AdapterParam表:ADID(Integer类型主键,外键关联Adapter.ADID)、PAN(String类型主键,对应实体
parameterName) - AdapterParamSelectionList表:ADID(Integer类型主键,外键关联AdapterParam.ADID)、PAN(String类型主键,外键关联AdapterParam.PAN)、code(String类型主键,对应表中
VAL列)
现有实体代码
Adapter实体
@Entity public class Adapter implements Serializable { @Id @Column(name = "ADID") private Integer adapterId; @OneToMany(mappedBy = "adapter", cascade = CascadeType.ALL, orphanRemoval = true) private Set<AdapterParam> adapterParams = new HashSet<>(); // Getter and Setter... }
AdapterParam实体
@Entity @IdClass(AdapterParamId.class) public class AdapterParam implements Serializable { public static class AdapterParamId implements Serializable { private static final long serialVersionUID = 1L; private Adapter adapter; private String parameterName; @Override public int hashCode() { return Objects.hash(adapter, parameterName); } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; AdapterParamId other = (AdapterParamId) obj; return Objects.equals(adapter, other.adapter) && Objects.equals(parameterName, other.parameterName); } } @Id @Column(name = "PAN") private String parameterName; @Id @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "ADID", referencedColumnName = "ADID") private Adapter adapter; @OneToMany(mappedBy = "adapterParam", cascade = CascadeType.ALL, orphanRemoval = true) private Set<AdapterParamSelectionList> adapterParamSelectList = new HashSet<>(); // Getter and Setter... }
AdapterParamSelectionList实体
@Entity @IdClass(AdapterParamSelectionListId.class) public class AdapterParamSelectionList implements Serializable { public static class AdapterParamSelectionListId implements Serializable { private static final long serialVersionUID = 1L; private AdapterParam adapterParam; private String code; @Override public int hashCode() { return Objects.hash(adapterParam, code); } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; AdapterParamSelectionList other = (AdapterParamSelectionList) obj; return Objects.equals(adapterParam, other.adapterParam) && Objects.equals(code, other.code); } } @Id @Column(name = "VAL") private String code; @Id @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "ADID", referencedColumnName = "ADID") // 此处报错 @JoinColumn(name = "PAN", referencedColumnName = "PAN") private AdapterParam adapterParam; // Getter and Setter... }
启动异常
org.hibernate.AnnotationException: Referenced column 'ADID' in '@JoinColumn' for 'de.example.AdapterParamSelectionList.id.adapterParam' is not mapped by any property of the target entity at org.hibernate.boot.model.internal.BinderHelper.findPropertiesByColumns(BinderHelper.java:501) at org.hibernate.boot.model.internal.BinderHelper.createSyntheticPropertyReference(BinderHelper.java:160) at org.hibernate.boot.model.internal.ToOneFkSecondPass.doSecondPass(ToOneFkSecondPass.java:114) at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.processFkSecondPassesInOrder(InFlightMetadataCollectorImpl.java:1850) at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.processSecondPasses(InFlightMetadataCollectorImpl.java:1764) at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.complete(MetadataBuildingProcess.java:334) at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.build(MetadataBuildingProcess.java:129) at org.hibernate.boot.internal.MetadataBuilderImpl.build(MetadataBuilderImpl.java:449) at org.hibernate.boot.internal.MetadataBuilderImpl.build(MetadataBuilderImpl.java:101) at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:966) at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1016)
问题原因
Hibernate 6对JPA规范的实现更严格,尤其是针对@IdClass的校验逻辑:
- Hibernate 5.6允许在
@IdClass中直接使用关联实体类型作为属性,但Hibernate 6要求必须使用关联实体的主键类型(复合主键则用对应IdClass或拆解后的主键属性)。 - 必须明确映射关联的所有主键列,确保关联列能对应到目标实体的主键属性上,不能再依赖隐式映射。
修复方案
1. 修正AdapterParamId类
将AdapterParamId中的Adapter adapter替换为Adapter的主键类型Integer,且属性名保持与AdapterParam实体中的关联主键属性名一致:
public static class AdapterParamId implements Serializable { private static final long serialVersionUID = 1L; // 属性名与实体中@Id关联属性名一致,类型为关联实体的主键类型 private Integer adapter; private String parameterName; @Override public int hashCode() { return Objects.hash(adapter, parameterName); } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; AdapterParamId other = (AdapterParamId) obj; return Objects.equals(adapter, other.adapter) && Objects.equals(parameterName, other.parameterName); } }
2. 修正AdapterParamSelectionListId类
由于AdapterParam是复合主键,AdapterParamSelectionListId需要包含AdapterParam的所有主键属性,加上自身的code属性:
public static class AdapterParamSelectionListId implements Serializable { private static final long serialVersionUID = 1L; // 对应AdapterParam的两个主键属性 private Integer adapter; private String parameterName; // 自身的主键属性 private String code; @Override public int hashCode() { return Objects.hash(adapter, parameterName, code); } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; AdapterParamSelectionListId other = (AdapterParamSelectionListId) obj; return Objects.equals(adapter, other.adapter) && Objects.equals(parameterName, other.parameterName) && Objects.equals(code, other.code); } }
3. 修正AdapterParamSelectionList实体
显式声明对应AdapterParam主键的列,通过@MapsId将这些列与IdClass中的属性关联,同时正确配置@ManyToOne关联:
@Entity @IdClass(AdapterParamSelectionListId.class) public class AdapterParamSelectionList implements Serializable { // 内部IdClass已修改,如上 @Id @Column(name = "VAL") private String code; // 显式声明对应AdapterParam主键的列,标记为@Id且不可插入/更新(由关联维护) @Id @Column(name = "ADID", insertable = false, updatable = false) private Integer adapter; @Id @Column(name = "PAN", insertable = false, updatable = false) private String parameterName; // 配置关联,通过@MapsId映射IdClass中的对应属性 @ManyToOne(fetch = FetchType.LAZY) @MapsId("adapter") // 映射IdClass中的adapter属性 @JoinColumn(name = "ADID", referencedColumnName = "ADID") @MapsId("parameterName") // 映射IdClass中的parameterName属性 @JoinColumn(name = "PAN", referencedColumnName = "PAN") private AdapterParam adapterParam; // Getter and Setter... }
修改完成后,Hibernate 6就能正确识别关联的主键列,符合JPA规范要求,启动时不会再抛出异常。
内容的提问来源于stack exchange,提问作者Xenon
相关产品推荐
相关产品推荐

