Inheritance#
Java 大量使用繼承與多型,允許定義提供共同介面的不同實作類別階層。然而,關聯式資料庫並未原生支援繼承,因此將物件導向的繼承對應到資料庫表是最明顯的 object-relational impedance mismatch 之一。
Martin Fowler 在 Patterns of Enterprise Application Architecture 中定義了三種將繼承對應到關聯式資料庫的方式,JPA 規範也對應提供了三種策略:
InheritanceType.SINGLE_TABLE— Single Table InheritanceInheritanceType.JOINED— Class Table Inheritance (Join Table)InheritanceType.TABLE_PER_CLASS— Concrete Table Inheritance (Table-per-class)
此外,JPA 也支援 @MappedSuperclass,讓繼承只存在於 Domain Model 而不對應到資料庫。
本章以一個論壇系統為範例:Board 為根 entity,Topic 為基礎類別,Post 和 Announcement 繼承自 Topic,TopicStatistics 則透過 @OneToOne 關聯到 Topic。

Figure 11.1: Domain Model Inheritance
Single Table#
Single table inheritance 是 JPA 的預設策略,將整個繼承階層放入單一資料庫表。
- 在
Topicentity 上標註@Inheritance或@Inheritance(strategy = InheritanceType.SINGLE_TABLE) - 子類別
Post和Announcement不需要額外的 mapping annotation,只需 Java 繼承語法 - 資料庫表包含所有子類別的欄位,並透過
DTYPEdiscriminator column 區分實體類型

Figure 11.2: Single table
多型查詢#
- 對
Topicentity 執行查詢時,Hibernate 會掃描單一表格,並根據DTYPE欄位將每一列對應到正確的子類別(Post或Announcement) @OneToMany基礎類別的關聯也能自動解析子類別型別- 載入
TopicStatistics並 join fetchTopic時,Hibernate 透過INNER JOIN即可建構正確的子類別物件參考
效能與資料完整性#
- 讀寫效能佳:只使用單一表格,即使搭配
@ManyToOne、@OneToOne等關聯也只需一次 join - 缺點:子類別專屬欄位無法設定
NOT NULLconstraint,因為所有子類別共用同一張表,其他子類別的欄位必然為 null,這違反了 Consistency 原則
Data Integrity Constraints#
由於 single table 策略無法直接使用 NOT NULL,可透過以下方式補強資料完整性:
CHECKconstraint(支援 Oracle、SQL Server、PostgreSQL、MySQL 8.0.16+):根據DTYPE值判斷特定欄位是否不為 null- Database trigger(適用於較舊版本的 MySQL):在
BEFORE INSERT和BEFORE UPDATEtrigger 中驗證子類別專屬欄位 - 應用層驗證:使用 Bean Validation(
@NotNull)或 JPA callback methods(@PrePersist、@PreUpdate)及@EntityListeners
CHECKconstraint 和 trigger 雖然較為冗長,但能在資料庫層面確保 single table inheritance 的資料完整性,是非常有用的防護機制。
Join Table#
Join table inheritance(InheritanceType.JOINED)將每個類別對應到獨立的表格,子類別表透過 foreign key 參照基礎類別表的 primary key。
- 在
Topicentity 上標註@Inheritance(strategy = InheritanceType.JOINED) - 子類別可使用
@PrimaryKeyJoinColumn定義 foreign key 欄位,預設使用子類別表的 primary key 同時作為 foreign key - 基礎類別資訊存於
topic表,子類別專屬內容分別存於post和announcement表

Figure 11.3: Join table
多型查詢#
- 查詢
Topic時,Hibernate 必須 LEFT OUTER JOIN 所有子類別表,並使用CASE WHEN判斷實際類型 - 載入
TopicStatistics及其Topic關聯時,需要 join 四張表(topic_statistics、topic、post、announcement) - Eager fetch
Board的topics集合時,也需要三個 join
效能考量#
- 優點:子類別欄位可設定
NOT NULLconstraint,資料完整性較佳 - 寫入較慢:每個子類別 entity 需要兩條
INSERT語句(基礎表 + 子類別表),index 記憶體佔用也較高 - 讀取較慢:多型查詢需要 N+1 個 join(N 為子類別數量),子類別越多,查詢效能越差
Table-per-class#
Table-per-class(InheritanceType.TABLE_PER_CLASS)讓每個子類別表都包含基礎類別的所有欄位,各表之間沒有 foreign key 關聯。
- 在
Topicentity 上標註@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) post和announcement表各自儲存完整的Topic屬性加上自身的專屬欄位topic表僅存放不屬於任何子類別的純Topic記錄

Figure 11.4: Table-per-class
多型查詢#
- 查詢
Topic時,Hibernate 使用UNION ALL合併基礎表和所有子類別表的結果 - 載入
TopicStatistics的Topic關聯同樣需要UNION ALL - Eager fetch
Board的topics集合時,需要 joinUNION ALL子查詢的結果
效能考量#
- 寫入較快:每個子類別只需一條
INSERT語句 - 讀取受限:多型查詢需要
UNION ALL,子類別越多效能越差 - Identity generator 不可用:不同子類別表的列可能共享相同 identifier,在多型
@ManyToOne或@OneToOne關聯中會產生衝突
Table-per-class 在處理多型查詢和關聯時效能最差。若每個子類別都儲存在獨立表格中,
@MappedSuperclass通常是更好的替代方案。
Mapped Superclass#
若基礎類別不需要作為獨立的 entity(例如不需要多型查詢),可使用 @MappedSuperclass 將 Topic 設為 abstract 類別,讓繼承只存在於 Java 層而不反映到資料庫。

Figure 11.5: Mapped superclass class diagram
Topic標註@MappedSuperclass而非@Entity- 基礎類別屬性會被合併到子類別表中(類似 table-per-class,但基礎類別不對應任何表)
- 因為
Topic不是 entity,無法使用多型查詢,Board也無法定義@OneToMany Topic集合 - 原本的
TopicStatistics必須拆分為PostStatistics和AnnouncementStatistics,各自關聯到對應的子類別

Figure 11.6: Mapped superclass
效能考量#
- 讀寫效能最佳:寫入只需一條
INSERT,讀取只需查詢單一表格 - 代價:放棄多型查詢與基礎類別的 entity 關聯能力
繼承策略最佳實踐#
Domain Model inheritance 是強大的概念,但應謹慎使用,僅在效益大於取捨時才採用。
| 策略 | 寫入效能 | 讀取效能 | 資料完整性 | 多型查詢 |
|---|---|---|---|---|
| Single Table | 最佳 | 最佳 | 需額外 CHECK/Trigger | 支援 |
| Join Table | 較慢(多條 INSERT) | 較慢(多 JOIN) | 佳(可用 NOT NULL) | 支援 |
| Table-per-class | 佳(單條 INSERT) | 差(UNION ALL) | 佳 | 支援但效能差 |
| Mapped Superclass | 最佳 | 最佳 | 佳 | 不支援 |
- Single table:讀寫效能最佳,適合子類別數量少且資料庫支援 trigger/CHECK constraint 的場景
- Join table:適合子類別數量少但各自有大量專屬欄位的場景;子類別數量增多時多型查詢效能顯著下降
- Table-per-class:多型查詢效能最差,若不需多型查詢建議改用
@MappedSuperclass - Mapped superclass:讀寫最高效但犧牲多型能力,適合子類別完全獨立運作的場景