單表策略 將整個繼承體系映射到一張表,通過判別欄區分子類型。本文通過實戰代碼演示其查詢、加載、保存行為及生成 SQL,揭示「查詢效能最強但表結構妥協」的特性。
欄位:ID(PK), NAME, RATE, SALARY, EMPLOYEE_TYPE(判別欄), COMPANY_ID(FK)
判別欄值:'HE' = HourlyEmployee, 'SE' = SalariedEmployee。子類專有欄位 (RATE/SALARY) 在另一類型記錄中為 NULL。
<discriminator> + <subclass><class name="mypack.Employee" table="EMPLOYEES">
<id name="id" column="ID"><generator class="increment"/></id>
<discriminator column="EMPLOYEE_TYPE" type="string"/> <!-- 關鍵 -->
<property name="name" column="NAME"/>
<many-to-one name="company" column="COMPANY_ID" class="mypack.Company"/>
<subclass name="mypack.HourlyEmployee" discriminator-value="HE">
<property name="rate" column="RATE" type="double"/>
</subclass>
<subclass name="mypack.SalariedEmployee" discriminator-value="SE">
<property name="salary" column="SALARY" type="double"/>
</subclass>
</class>
List results = session.find("from HourlyEmployee");
SQL:SELECT * FROM EMPLOYEES WHERE EMPLOYEE_TYPE='HE';
✅ 無 Join,極快。同時加載關聯的 Company (N+1 或 Join Fetch)。
List results = session.find("from Employee");
SQL:SELECT * FROM EMPLOYEES;
Hibernate 讀取 EMPLOYEE_TYPE 實例化對應子類對象。✅ 單表全掃描,多態查詢效能最優。
配置 lazy="true" 時需手動初始化:
Company c = (Company) session.load(Company.class, id);
Hibernate.initialize(c.getEmployees()); // 觸發加載
生成 SQL 同多態查詢,按 COMPANY_ID 過濾。
session.save(new HourlyEmployee("Mary", 300, company));
SQL:INSERT INTO EMPLOYEES (ID, NAME, RATE, EMPLOYEE_TYPE, COMPANY_ID) VALUES (5, 'Mary', 300, 'HE', 1);
SALARY 欄位自動為 NULL。✅ 單 INSERT,效能最優。
| 優點 | 缺點 |
|---|---|
| 🚀 多態查詢/關聯效能最佳 (無 Join) 🚀 保存/更新單表操作 🚀 映射配置簡單 |
🛑 違反 3NF (稀疏表、大量 NULL) 🛑 子類非空字段無法加 NOT NULL 約束 🛑 表結構隨繼承擴展而膨脹 🛑 判別欄需額外維護 |
EMPLOYEE_TYPE 建立索引,加速多態查詢過濾。單表策略是「以空間換時間」的經典權衡,理解其 SQL 生成機制有助於在架構評審中做出正確選型。