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

Spring Data JPA下@IdClass处理共享FK/PK的插入异常问题求助

问题描述

数据库表结构

遗留系统的两张表结构如下:

A: PK (idA, ente),
B: PK(idB, ente), FK(idA, ente) --> A

目标插入逻辑

希望通过以下Spring Data JPA代码完成数据插入:

A a = new A();
a.setEnte("E1");
a = repA.save(a);

B b = new B();
b.setA(a);
b = repB.save(b);

当前实体实现代码

实体A

@Getter
@ToString
@Setter
@Entity
@Table(name = "Users")
@IdClass(APK.class)
@NoArgsConstructor
public class A implements Serializable {

    @Id
    @org.hibernate.annotations.GenericGenerator(name = "incrementGenerator", strategy = "org.hibernate.id.IncrementGenerator")
    @GeneratedValue(generator="incrementGenerator")
    @Column(name = "idA", columnDefinition = "int(10)", nullable = false)
    private Integer idA;

    @Id
    @Column(name = "ente", columnDefinition = "char(6)", nullable = false)
    public String ente;
}

实体B

@Getter
@ToString
@Setter
@Entity
@Table(name = "address")
@IdClass(BPK.class)
@NoArgsConstructor
public class B  implements Serializable{
    
    @ManyToOne
    @JoinColumns({
        @JoinColumn(name="idA", referencedColumnName="idA", nullable=false, insertable=false, updatable=false),
        @JoinColumn(name="ente", referencedColumnName="ente", nullable=false, insertable=false, updatable=false)
        })
    private A a;

    @Id
    @org.hibernate.annotations.GenericGenerator(name = "incrementGenerator", strategy = "org.hibernate.id.IncrementGenerator")
    @GeneratedValue(generator="incrementGenerator")
    @Column(name = "idB", columnDefinition = "int(10)", nullable = false)
    private Integer idB;

    @Id
    @Column(insertable=false, updatable=false, unique=true, nullable=false, length=6)
    private String ente;
}

遇到的问题

  • 执行save(b)时,idA和ente字段未被插入到表B;
  • 若将@ManyToOne中的insertable改为true,会触发ente字段相关的SQL错误。

解决方案

核心问题是实体B的ente字段同时作为主键和外键,当前配置导致JPA无法同步关联属性到主键字段,以下是具体调整方案:

1. 修改实体B的映射配置

调整关联关系和主键字段的配置,同时添加属性同步逻辑:

@Getter
@ToString
@Setter
@Entity
@Table(name = "address")
@IdClass(BPK.class)
@NoArgsConstructor
public class B implements Serializable {
    
    @ManyToOne
    @JoinColumns({
        // 允许JPA通过关联实体插入idA
        @JoinColumn(name = "idA", referencedColumnName = "idA", nullable = false),
        // ente的insertable/updatable设为false,避免与主键字段插入冲突
        @JoinColumn(name = "ente", referencedColumnName = "ente", nullable = false, insertable = false, updatable = false)
    })
    private A a;

    @Id
    @org.hibernate.annotations.GenericGenerator(name = "incrementGenerator", strategy = "org.hibernate.id.IncrementGenerator")
    @GeneratedValue(generator = "incrementGenerator")
    @Column(name = "idB", columnDefinition = "int(10)", nullable = false)
    private Integer idB;

    @Id
    // 移除insertable/updatable=false,允许手动赋值插入
    @Column(name = "ente", columnDefinition = "char(6)", nullable = false)
    private String ente;

    // 重写setA方法,同步关联实体的ente到当前主键字段
    @Override
    public void setA(A a) {
        this.a = a;
        if (a != null) {
            this.ente = a.getEnte();
        }
    }
}

关键修改说明

  • 关联字段idA:去掉insertable=false, updatable=false,让JPA可以通过关联实体A自动插入idA值;
  • 主键字段ente:移除insertable/updatable=false,允许该字段被赋值;
  • 同步逻辑:重写setA方法,设置关联实体A时自动将A的ente赋值给B的ente主键字段,确保主键与外键值一致。

2. 确保IdClass类正确实现

APK和BPK必须实现Serializable,并重写equals和hashCode方法,示例BPK代码:

import java.util.Objects;

public class BPK implements Serializable {
    private Integer idB;
    private String ente;

    public BPK() {}

    public BPK(Integer idB, String ente) {
        this.idB = idB;
        this.ente = ente;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        BPK bpk = (BPK) o;
        return Objects.equals(idB, bpk.idB) && Objects.equals(ente, bpk.ente);
    }

    @Override
    public int hashCode() {
        return Objects.hash(idB, ente);
    }

    // 需添加getter和setter,否则JPA无法识别主键字段
    public Integer getIdB() {
        return idB;
    }

    public void setIdB(Integer idB) {
        this.idB = idB;
    }

    public String getEnte() {
        return ente;
    }

    public void setEnte(String ente) {
        this.ente = ente;
    }
}

3. 验证插入逻辑

原插入代码无需修改,执行流程:

  1. 保存A后,A的idA(自动生成)和ente已赋值;
  2. 调用b.setA(a)时,重写的方法自动将A的ente同步到B的ente主键字段;
  3. 保存B时,JPA自动插入idA(来自关联实体)、ente(同步后的主键值)和自动生成的idB,完成正确插入。

补充说明

  • 不能将ente的insertable设为true,因为B的ente既是主键又是外键,同时设置会导致JPA尝试插入两次ente字段,触发SQL重复列错误;
  • 手动同步关联属性是@IdClass模式下处理复合主键关联的常用方式,因为@IdClass无法像@EmbeddedId那样直接嵌入关联实体的主键。

内容的提问来源于stack exchange,提问作者Tiziano Ruaro

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 18:01:25