You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 12:54:51