SINGLE_TABLE 是 JPA 預設繼承策略,將整個繼承階層映射到單一資料表,透過「鑑別欄位」區分子類型。優點是查詢無需 Join、效能最佳;缺點是子類獨有欄位會產生大量空值。本文示範完整映射配置與自動鑑別值賦予機制。
account_id (PK) | balance | account_type | interest_rate | overdraft_limit
account_type:鑑別欄位,'C'=CheckingAccount(活期)、'S'=SavingsAccount(儲蓄)interest_rate:僅儲蓄帳戶有效,活期為 NULLoverdraft_limit:僅活期帳戶有效,儲蓄為 NULL
@Entity
@Table(name="t_accounts")
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="account_type") // 指定鑑別欄位
public class Account {
protected String id;
protected float balance;
protected String type;
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
@Column(name="account_id")
public String getId() { return id; }
public void setId(String id) { this.id = id; }
public float getBalance() { return balance; }
public void setBalance(float balance) { this.balance = balance; }
@Column(name="account_type", insertable=false, updatable=false)
public String getType() { return type; }
public void setType(String type) { this.type = type; }
}
| 註解 | 作用 |
|---|---|
@Inheritance(SINGLE_TABLE) | 啟用單表策略(預設值,可省略) |
@DiscriminatorColumn(name="account_type") | 指定鑑別欄位,容器據此決定實例化哪個子類 |
@Column(insertable=false, updatable=false) | 鑑別欄位由容器自動維護,禁止手動寫入 |
注意:若不設 insertable=false, updatable=false,執行時會拋出 MappingException: Repeated column in mapping...。
@Entity
@DiscriminatorValue("C") // 指定 account_type = 'C'
public class CheckingAccount extends Account {
private double overdraftLimit;
public double getOverdraftLimit() { return overdraftLimit; }
public void setOverdraftLimit(double overdraftLimit) { this.overdraftLimit = overdraftLimit; }
}
@Entity
@DiscriminatorValue("S") // 指定 account_type = 'S'
public class SavingsAccount extends Account {
private double interestRate;
public double getInterestRate() { return interestRate; }
public void setInterestRate(double interestRate) { this.interestRate = interestRate; }
}
// 新增活期帳戶
CheckingAccount ca = new CheckingAccount();
ca.setBalance(342);
ca.setOverdraftLimit(120);
em.persist(ca); // 自動將 account_type 寫入 'C'
// 新增儲蓄帳戶
SavingsAccount sa = new SavingsAccount();
sa.setBalance(200);
sa.setInterestRate(321);
em.persist(sa); // 自動將 account_type 寫入 'S'
開發者完全不需手動設定 account_type,容器根據實體類型自動填入對應 @DiscriminatorValue。
| JPQL | SQL 對應 | 結果類型 |
|---|---|---|
from Account | SELECT * FROM t_accounts | 混合 List<Account> (含 CheckingAccount、SavingsAccount 實例) |
from CheckingAccount | SELECT * FROM t_accounts WHERE account_type='C' | List<CheckingAccount> |
from SavingsAccount | SELECT * FROM t_accounts WHERE account_type='S' | List<SavingsAccount> |
| 優點 | 缺點 |
|---|---|
| 查詢最快(無 Join) | 稀疏欄位產生大量 NULL,浪費儲存空間 |
| 插入/更新單表,簡單高效 | 子類獨有欄位無法加 NOT NULL 約束 |
| 多型查詢自然支援 | 表結構變更影響所有子類,耦合度高 |
若未來需遷移至 JOINED 策略:
@Inheritance(JOINED)、加上 @PrimaryKeyJoinColumn@DiscriminatorColumn 與 insertable/updatable=false