前言
Hibernate 提供三種繼承映射策略,各有優劣,選型直接影響資料庫結構、查詢效能與維護成本。本文以 TItem (父) → TBook / TDVD (子) 為例,詳細對比三種策略的映射配置、生成表結構、查詢 SQL 及適用場景。
一、領域模型
public class TItem implements Serializable {
private int id;
private String manufacture;
private String name;
// getter/setter
}
public class TBook extends TItem { private int pageCount; }
public class TDVD extends TItem { private String regionCode; }
二、策略一:Table Per Concrete Class (隱式多態 / 獨立表)
2.1 映射配置 (三個獨立 .hbm.xml)
<class name="TItem" table="ITEM">
<id name="id"><generator class="native"/></id>
<property name="name"/> <property name="manufacture"/>
</class>
<class name="TBook" table="BOOK">
<id name="id"><generator class="native"/></id>
<property name="name"/> <property name="manufacture"/> <property name="pageCount"/>
</class>
<class name="TDVD" table="DVD">
<id name="id"><generator class="native"/></id>
<property name="name"/> <property name="manufacture"/> <property name="regionCode"/>
</class>
2.2 特點
- 每個具體類一張表,父類屬性在每張子表冗余
- 主鍵獨立生成,跨表 ID 可能重複
- 多態查詢
from TItem 需 UNION 多表,效能差
- 修改父類屬性需同步修改所有子表映射,維護成本高
- 適用:子類極少、極少多態查詢、歷史系統遷移
三、策略二:Joined Subclass (連接表 / 正規化)
3.1 映射配置 (單一 .hbm.xml)
<class name="TItem" table="ITEM" polymorphism="explicit">
<id name="id"><generator class="native"/></id>
<property name="name"/> <property name="manufacture"/>
<joined-subclass name="TBook" table="TBOOK">
<key column="id"/>
<property name="pageCount"/>
</joined-subclass>
<joined-subclass name="TDVD" table="TDVD">
<key column="id"/>
<property name="regionCode"/>
</joined-subclass>
</class>
3.2 生成表結構
| 表名 | 欄位 |
| ITEM | id (PK), name, manufacture |
| TBOOK | id (PK, FK→ITEM), pageCount |
| TDVD | id (PK, FK→ITEM), regionCode |
3.3 特點
- 資料正規化,無冗余,修改父類屬性只改 ITEM 表
- 共享主鍵 (Foreign Key Generator),ID 全局唯一
- 多態查詢產生
LEFT OUTER JOIN,查詢較慢 (隨子類增加)
- 插入需寫入多表,事務開銷稍大
- 適用:子類較多、頻繁修改父類、需正規化設計、可接受 JOIN 開銷
四、策略三:Single Table with Discriminator (單表 / 判別欄位)
4.1 映射配置
<class name="TItem" table="ITEM" polymorphism="explicit">
<id name="id"><generator class="native"/></id>
<discriminator column="category" type="string"/>
<property name="name"/> <property name="manufacture"/>
<subclass name="TBook" discriminator-value="BOOK">
<property name="pageCount"/>
</subclass>
<subclass name="TDVD" discriminator-value="DVD">
<property name="regionCode"/>
</subclass>
</class>
4.2 生成表結構
| 欄位 | 說明 |
| id (PK) | 主鍵 |
| category | 判別欄位:'BOOK' 或 'DVD' |
| name, manufacture | 父類屬性 |
| pageCount | TBook 專屬,DVD 行為 NULL |
| regionCode | TDVD 專屬,BOOK 行為 NULL |
4.3 特點
- 單表無 JOIN,查詢/插入/更新最快
- 子類專屬欄位設為
NOT NULL 受限 (需可空)
- 子類過多導致稀疏表、欄位爆炸、違反正規化
- 磁碟空間浪費 (大量 NULL)
- 適用:子類少 (≤5)、屬性差異小、極高讀寫效能要求、簡單業務
五、三策略綜合比較表
| 維度 | Table Per Class | Joined Subclass | Single Table |
| 表數量 | N (每具體類一張) | N+1 (父表+各子表) | 1 |
| 資料冗余 | 高 (父屬性重複) | 無 (正規化) | 無 (但大量 NULL) |
| 主鍵策略 | 獨立生成 | 共享 (Foreign Key) | 單一生成 |
| 多態查詢 SQL | UNION ALL | LEFT OUTER JOIN | 單表掃描 + WHERE discriminator |
| 查詢效能 | 最差 | 中等 | 最佳 |
| 寫入效能 | 單表 | 多表事務 | 單表最快 |
| Schema 變更影響 | 需改所有子表 | 只改父表 | 只改單表 (鎖表風險) |
| 推薦場景 | 極少多態、遷移專案 | 標準企業應用、正規化優先 | 高併發讀寫、子類少 |
六、註解式配置對應 (JPA / Hibernate Annotations)
// Joined
@Entity @Inheritance(strategy = InheritanceType.JOINED)
public class TItem { @Id @GeneratedValue Long id; ... }
@Entity public class TBook extends TItem { int pageCount; }
// Single Table
@Entity @Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "category")
public class TItem { ... }
@Entity @DiscriminatorValue("BOOK") public class TBook extends TItem { ... }
// Table Per Class
@Entity @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class TItem { ... }
七、總結建議
- 預設優先 Joined Subclass:平衡正規化、效能、維護性。
- 讀寫極高頻、子類穩定少見 → Single Table。
- 避免 Table Per Class,除非歷史包袱或極特殊需求。
- 實際專案中常混用:核心領域模型用 Joined,日誌/審計類用 Single Table。