關係資料庫系統本身複雜,加上 Hibernate O/R 映射層更易出現問題。本文總結近期遇到的問題與解決方法,為系列第三篇,專講 one-to-many (一對多) 關聯中持久化類別的三個關鍵方法實作:equals()、hashCode()、無參數建構子。
上篇《在 Hibernate 中正確實現關聯關係中的級聯操作》說明如何正確宣告關聯與使用持久化方法。為保證從 User 到 Preference 級聯正確運行,必須正確實作上述三個方法。
務必使用能唯一區分物件的業務屬性(Business Key / Natural Key)計算 hash code 與相等比較,而非 Hibernate 自動生成的 Primary Key(Surrogate Key)。
主鍵雖唯一,但視情況而定:
equals()/hashCode(),計算大幅簡化。java.lang.NullPointerException。Hibernate 實例化實體類別時必須有無參數建構子(可為 protected 或 package-private)。若 Preference 缺少無參建構子,級聯持久化時會拋出:
org.hibernate.InstantiationException: No default constructor for entity: xxx
public class Preference implements Serializable {
private Long id; // Surrogate Key,Hibernate 自動生成
private String userId; // Business Key(業務鍵)
private String settingKey;
private String settingValue;
// 必須提供無參數建構子
public Preference() {}
// 使用業務鍵實作 equals/hashCode
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof Preference)) return false;
Preference other = (Preference) obj;
// 使用 userId + settingKey 組合作為業務鍵
return Objects.equals(this.userId, other.userId)
&& Objects.equals(this.settingKey, other.settingKey);
}
@Override
public int hashCode() {
return Objects.hash(userId, settingKey);
}
// getter/setter...
}
| 錯誤 | 後果 |
|---|---|
| 使用 Surrogate Key 實作 equals/hashCode | 暫態物件比較失敗、Set/Map 行為異常、級聯操作失效 |
| 缺少無參數建構子 | InstantiationException,無法級聯持久化 |
| equals/hashCode 不一致 | 違反 Java 契約,導致 Collection 行為不可預期 |
| 業務鍵包含可變欄位 | 物件加入 Set/Map 後修改鍵值導致找不到元素 |
Objects.equals/Objects.hash (Java 7+) 簡化實作並避免 NPEequals 與 hashCode 使用相同欄位組合final 或不可變,確保雜湊碼穩定Serializable 介面支援分散式環境