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. 验证插入逻辑
原插入代码无需修改,执行流程:
- 保存A后,A的
idA(自动生成)和ente已赋值; - 调用
b.setA(a)时,重写的方法自动将A的ente同步到B的ente主键字段; - 保存B时,JPA自动插入
idA(来自关联实体)、ente(同步后的主键值)和自动生成的idB,完成正确插入。
补充说明
- 不能将
ente的insertable设为true,因为B的ente既是主键又是外键,同时设置会导致JPA尝试插入两次ente字段,触发SQL重复列错误; - 手动同步关联属性是
@IdClass模式下处理复合主键关联的常用方式,因为@IdClass无法像@EmbeddedId那样直接嵌入关联实体的主键。
内容的提问来源于stack exchange,提问作者Tiziano Ruaro
相关产品推荐
相关产品推荐

