Hibernate多主键外键关联问题:Table1.save()无法正常POST/PUT
Hibernate联合主键关联保存问题排查与修复
表结构定义
CREATE TABLE table1 ( id BIGSERIAL PRIMARY KEY NOT NULL, param1 varchar(255) ); CREATE TABLE table2 ( id_table1 BIGINT NOT NULL, pk2 varchar(255) NOT NULL, param2 varchar(255) NOT NULL, CONSTRAINT fk_table1 FOREIGN KEY (id_table1) REFERENCES merging_configuration(id) ); ALTER TABLE merging_configuration_type ADD PRIMARY KEY (id_table1, pk2);
注:SQL存在两处笔误,
REFERENCES merging_configuration(id)应改为REFERENCES table1(id),ALTER TABLE merging_configuration_type应改为ALTER TABLE table2,需修正后保证表结构逻辑正确。
现有Hibernate实体类
Table1实体
@Entity(name = "table1") public class Table1 { @Id private Long id; private String param1; @OneToMany @JoinColumn(name = "id_table1") private List<Table2> table2list; // 构造方法、getter、setter… }
Table2实体
@Entity(name = "table2") public class Table2 { @EmbeddedId private Table2Id id; private String param2; }
Table2Id嵌入主键类
@Embeddable public class Table2Id implements Serializable { @Column(name = "id_table1") private Long idTable1; @Column(name = "pk2") private String pk2; }
遇到的问题
- 当
table2List中的联合键与数据库现有数据一致时:param1可正常更新,但param2无法更新; - 当
table2List不包含数据库现有数据时:日志报错ERROR: a NULL value violates the NOT NULL constraint of the column "id_table1" in the relation "table2". Meaning: The failed row contains (null, myPK2, myParam2); - 当
table2List包含数据库不存在的数据时:save方法抛出错误Unable to find path.Table2 with id Table2Id(idTableB=1, pk2=myPK2)。
问题原因与修复方案
核心问题分析
- 未配置级联策略:
@OneToMany默认无任何级联操作,导致Table1的save操作无法同步到关联的Table2; - 联合主键与外键未绑定:Table2的联合主键中
id_table1未与Table1主键建立映射,Hibernate无法自动填充外键值; - 嵌入主键类未重写
equals/hashCode:Serializable类型的嵌入主键必须实现这两个方法,否则Hibernate无法正确识别实体唯一性; - 集合未初始化:未初始化的集合可能导致空指针或Hibernate无法正确处理关联关系。
修改后的实体类代码
Table1实体
@Entity(name = "table1") public class Table1 { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) // 适配BIGSERIAL自增主键 private Long id; private String param1; @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) @JoinColumn(name = "id_table1", referencedColumnName = "id") private List<Table2> table2list = new ArrayList<>(); // 初始化集合 // 构造方法、getter、setter… }
cascade = CascadeType.ALL:让Table1的增删改查操作级联到关联的Table2;orphanRemoval = true:自动删除从table2list中移除的Table2实体;@GeneratedValue:让Hibernate自动获取数据库生成的自增主键。
Table2实体
@Entity(name = "table2") public class Table2 { @EmbeddedId private Table2Id id; private String param2; @MapsId("idTable1") // 将嵌入主键中的idTable1与当前关联的Table1主键绑定 @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "id_table1", insertable = false, updatable = false) // 避免重复维护外键字段 private Table1 table1; // 用于创建关联实体的构造方法 public Table2(Table1 table1, String pk2) { this.id = new Table2Id(table1.getId(), pk2); this.table1 = table1; } // Hibernate必需的无参构造方法 public Table2() {} // getter、setter… }
@MapsId:将嵌入主键中的idTable1字段与Table1的主键关联,Hibernate会自动填充该值;- 新增带参构造方法:确保创建Table2时正确初始化联合主键与关联关系。
Table2Id嵌入主键类
@Embeddable public class Table2Id implements Serializable { @Column(name = "id_table1") private Long idTable1; @Column(name = "pk2") private String pk2; // 无参构造方法 public Table2Id() {} // 带参构造方法 public Table2Id(Long idTable1, String pk2) { this.idTable1 = idTable1; this.pk2 = pk2; } // 必须重写equals和hashCode,确保实体唯一性判断正确 @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Table2Id table2Id = (Table2Id) o; return Objects.equals(idTable1, table2Id.idTable1) && Objects.equals(pk2, table2Id.pk2); } @Override public int hashCode() { return Objects.hash(idTable1, pk2); } // getter、setter… }
使用示例
保存或更新Table1时,需确保关联的Table2正确绑定:
// 创建Table1实体 Table1 table1 = new Table1(); table1.setParam1("updated_param"); // 创建关联的Table2实体(已有数据更新) Table2 existingTable2 = new Table2(table1, "existing_pk2"); existingTable2.setParam2("updated_param2"); // 创建新的Table2实体 Table2 newTable2 = new Table2(table1, "new_pk2"); newTable2.setParam2("new_param2"); // 添加到集合 table1.getTable2list().add(existingTable2); table1.getTable2list().add(newTable2); // 保存操作 table1Repository.save(table1);
内容的提问来源于stack exchange,提问作者Gilles Schorter
相关产品推荐
相关产品推荐

