Hibernate 一對多雙向關聯中,「一」的一方 (Group) 持有集合,「多」的一方 (User) 持有單向引用。保存時拋出空指針,根因是「一」的一方集合未初始化,且雙向關聯維護順序錯誤。
Group g = new Group();
g.setName("g1");
// ❌ g.getUsers() 返回 null,未初始化集合
g.getUsers().add(u); // NPE!
User u = new User();
u.setName("u1");
u.setGroup(g); // 設置反向引用
g.getUsers().add(u); // 這裡其實已晚了,集合可能仍為 null
// Group.java (一的一方)
@Entity
public class Group {
@Id @GeneratedValue
private Long id;
private String name;
@OneToMany(mappedBy = "group", cascade = CascadeType.ALL, orphanRemoval = true)
// 初始化集合,避免 NPE
private Set users = new HashSet<>();
// 便利方法:雙向綁定 (核心最佳實踐)
public void addUser(User user) {
users.add(user);
user.setGroup(this); // 維護反向引用
}
public void removeUser(User user) {
users.remove(user);
user.setGroup(null);
}
// getters/setters
}
// User.java (多的一方)
@Entity
public class User {
@Id @GeneratedValue
private Long id;
private String name;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "group_id")
private Group group;
// getters/setters
}
Group g = new Group();
g.setName("g1");
User u1 = new User();
u1.setName("u1");
User u2 = new User();
u2.setName("u2");
// ✅ 使用便利方法,自動雙向綁定
g.addUser(u1);
g.addUser(u2);
// 只需保存「一」的一方,cascade=ALL 會級聯保存「多」的一方
session.save(g);
transaction.commit();
| 問題 | 原因 | 解決 |
|---|---|---|
| 集合為 null | 實體類字段未初始化 | private Set |
| 雙向關聯不同步 | 只設置單向,另一邊為 null | 實體類提供 addUser/removeUser 便利方法,內部雙向綁定 |
| 保存報錯 | 未配置 cascade、或雙向不一致 | cascade = CascadeType.ALL、orphanRemoval = true |
| 無限遞歸序列化 | toString/JSON 序列化雙向循環 | @JsonIgnore (Jackson) / @ToString.Exclude (Lombok) 標記反向引用 |
mappedBy = "group" 表示關聯由 User 端的 group 字段維護 (外鍵在 User 表)users 集合是視圖,非數據庫物理列new HashSet<>())addXxx/removeXxx 便利方法,內部雙向綁定cascade = ALL, orphanRemoval = trueequals/hashCode 僅基於 ID (或業務鍵),不包含集合字段@JsonIgnore / @ToString.Exclude)掌握「便利方法雙向綁定」模式,是 Hibernate/JPA 避開 90% 一對多坑點的核心鑰匙。