Hibernate 作為標準 ORM 框架,其核心價值在於將關係數據庫表映射為面向對象領域模型。關聯映射是建模的關鍵難點,本文系統梳理四大關聯類型、單雙向配置差異、主鍵/外鍵策略、級聯操作與 inverse 維護機制,提供生產級最佳實踐。
<one-to-one>、<many-to-one>、<one-to-many>、<many-to-many>、<set>/<list> 等)。業務場景:用戶 ↔ 用戶詳情、訂單 ↔ 支付單、員工 ↔ 工牌。
| 策略 | 特點 | 映射關鍵點 | 優缺點 |
|---|---|---|---|
| 主鍵關聯 (共享主鍵) | 兩表主鍵值相同,主表主鍵即外鍵 | <id><generator class="foreign"><param name="property">address</param></generator></id><one-to-one name="address" constrained="true"/> | ✅ 表結構緊湊、無額外外鍵列 ❌ 擴展性差,難以改為一對多 ❌ 從表必須依賴主表主鍵生成 |
| 唯一外鍵關聯 (推薦) | 從表有獨立主鍵 + 唯一外鍵指向主表 | 主表:<one-to-one name="address" property-ref="user"/>從表: <many-to-one name="user" class="User" unique="true" column="user_id"/> | ✅ 靈活、符合範式、易擴展為一對多 ✅ 從表可獨立生成主鍵 |
單向 vs 雙向:單向僅主表配置 <one-to-one>;雙向雙表均配置,從表額外需 <many-to-one unique="true">。
業務場景:部門 ↔ 員工、分類 ↔ 商品、用戶 ↔ 訂單。
| 方向 | 配置方 | 關鍵映射 | 外鍵維護者 |
|---|---|---|---|
| 單向 (一→多) | 一的一方 (Department) | <set name="emps"><key column="dept_id"/><one-to-many class="Employee"/></set> | 一的一方 (發 UPDATE 更新員工 dept_id) |
| 單向 (多→一) | 多的一方 (Employee) | <many-to-one name="dept" column="dept_id"/> | 多的一方 (發 UPDATE 更新自身 dept_id) |
| 雙向 (推薦) | 雙方均配置 | 一:<set name="emps" inverse="true">...</set>多: <many-to-one name="dept" column="dept_id"/> | 多的一方 (inverse="true" 放棄一的一方維護) |
核心原則:雙向關聯必須設置 inverse="true" 於集合端 (一的一方),由關聯擁有者 (多的一方) 維護外鍵,否則產生冗余 UPDATE(先 INSERT 再 UPDATE 外鍵)。
業務場景:學生 ↔ 課程、用戶 ↔ 角色、商品 ↔ 標籤。
必須通過**中間表** 實現,雙向配置:
<set name="courses" table="student_course" inverse="true">
<key column="student_id"/>
<many-to-many class="Course" column="course_id"/>
</set>
<set name="students" table="student_course">
<key column="course_id"/>
<many-to-many class="Student" column="student_id"/>
</set>
關鍵點:table 指定中間表名,**雙方 table 必須一致**;inverse="true" 僅配置在**一方** 集合上(通常非擁有方),另一方負責維護中間表記錄增刪。
| 值 | 觸發操作 | 適用關聯 | 說明 |
|---|---|---|---|
| none | 無 | 所有 | 默認值 |
| save-update | save()/update()/saveOrUpdate() | <one-to-one>、<one-to-many> | 持久化瞬時/遊離對象 |
| delete | delete() | <one-to-one>、<one-to-many> | 刪除關聯對象 |
| all | save-update + delete | <one-to-one>、<one-to-many> | 全生命週期級聯 |
| delete-orphan | 從集合移除/設置 null | 僅 <one-to-many> | 孤兒刪除,子對象無父則亡 |
| lock/refresh/evict/replicate/merge/persist | 對應 Session 方法 | 特定場景 | 較少直接配置 |
<set>、<list>、<bag> 等集合標簽,**不可用於 <many-to-one>、<one-to-one>**<set inverse="true">,多的一方 <many-to-one> 維護外鍵。inverse="true",另一方維護中間表。
<set name="emps" cascade="save-update" inverse="true">
<key column="dept_id"/>
<one-to-many class="Employee"/>
</set>
<many-to-one name="dept" column="dept_id" class="Department"/>
解讀:保存部門時級聯保存員工 (cascade);外鍵 dept_id 由員工端維護 (inverse=true);刪除部門**不**級聯刪除員工 (無 delete),避免誤刪業務數據。
| 屬性 | 取值 | 行為 | 適用場景 |
|---|---|---|---|
| lazy | true (默認) | 代理/延遲加載,首次訪問發 SQL | 集合、單端關聯,防止過度加載 |
| lazy | false | 立即加載,同查詢發 LEFT JOIN | 極高頻訪問、小數據量 |
| fetch | select (默認) | 單獨 SELECT 加載 | 通用 |
| fetch | join | 外連接即時加載 | 需消除 N+1、數據量可控 |
| fetch | subselect | 子查詢批量加載集合 | 批量加載優化 N+1 |
| 關聯類型 | 單向配置 | 雙向額外配置 | 外鍵/中間表維護者 |
|---|---|---|---|
| 一對一主鍵 | 主表 <one-to-one constrained="true"> | 從表 <one-to-one constrained="true"> + generator foreign | 主表 (共享主鍵) |
| 一對一外鍵 | 主表 <many-to-one unique="true"> | 主表加 <one-to-one>,從表加 <many-to-one unique="true"> | 主表 (外鍵在主表) |
| 一對多 | 一的一方 <set><one-to-many> | 多的一方 <many-to-one> + 一的一方 inverse="true" | 多的一方 |
| 多對多 | 一方 <set table="mid"><many-to-many> | 另一方同樣 <set table="mid"> + 一方 inverse="true" | 非 inverse 一方 |
| 多對一 | 多的一方 <many-to-one> | 一的一方 <set inverse="true"> | 多的一方 |
save-update,刪除由業務邏輯顯式控制。fetch join 或實體映射 fetch="join",避免循環查詢。@OneToMany、@JoinColumn、@ManyToMany),類型安全、重構友好、IDE 支持完善。Hibernate 關聯映射的本質是 **「對象圖導航 ↔ 關係表外鍵」的雙向轉換規則**。透徹理解 inverse 決定「誰發 SQL」、cascade 決定「誰管生命週期」、fetch/lazy 決定「何時加載」,配合合理的領域建模(聚合根、值對象、實體邊界),才能駕馭複雜業務場景下的數據持久層。註解化、Spring Data JPA 集成、二級緩存優化,是後續進階方向。