繼承映射策略中,JOINED 將父類與各子類分別映射到不同資料表,透過主鍵關聯查詢。適用於:子類擁有大量獨有欄位、需避免單表過多空值、子類資料存取頻率差異大的場景。本文完整示範資料表設計、實體類別定義與測試程式碼。
account_id (PK) | balance | account_type

account_id (PK, FK) | overdraft_limit

account_id (PK, FK) | interest_rate

關鍵點:子表 account_id 同時為主鍵與外鍵,參考父表 account_id,實現 1:1 關聯。
@Entity
@Table(name="t_myaccounts")
@Inheritance(strategy=InheritanceType.JOINED)
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")
public String getType() { return type; }
public void setType(String type) { this.type = type; }
}
關鍵註解:@Inheritance(strategy=InheritanceType.JOINED) 啟用 JOINED 策略。
@Entity
@Table(name="t_checkingaccount")
@PrimaryKeyJoinColumn(name="account_id") // 指定共享主鍵欄位
public class MyCheckingAccount extends Account {
private double overdraftLimit;
public MyCheckingAccount() {
setType("C"); // 建構子預設類型值
}
@Column(name="overdraft_limit")
public double getOverdraftLimit() { return overdraftLimit; }
public void setOverdraftLimit(double overdraftLimit) { this.overdraftLimit = overdraftLimit; }
}
@Entity
@Table(name="t_savingsaccount")
@PrimaryKeyJoinColumn(name="account_id")
public class MySavingsAccount extends Account {
private double interestRate;
public MySavingsAccount() {
setType("S");
}
@Column(name="interest_rate")
public double getInterestRate() { return interestRate; }
public void setInterestRate(double interestRate) { this.interestRate = interestRate; }
}
// 查詢活期帳戶餘額
System.out.println(
((MyCheckingAccount) em.createQuery("from MyCheckingAccount where id=12")
.getSingleResult()).getBalance()
);
// 新增活期帳戶
MyCheckingAccount ca = new MyCheckingAccount();
ca.setBalance(342);
ca.setOverdraftLimit(120);
em.persist(ca); // 自動寫入 t_myaccounts 與 t_checkingaccount
// 新增儲蓄帳戶
MySavingsAccount sa = new MySavingsAccount();
sa.setBalance(200);
sa.setInterestRate(321);
em.persist(sa); // 自動寫入 t_myaccounts 與 t_savingsaccount
| 策略 | 註解 | 資料表數 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|---|---|
| SINGLE_TABLE | @Inheritance(SINGLE_TABLE) @DiscriminatorColumn | 1 | 查詢快、無 Join | 空值多、欄位耦合 | 欄位少、查詢頻繁、子類差異小 |
| JOINED | @Inheritance(JOINED) @PrimaryKeyJoinColumn | 1+N | 正規化、無空值、擴充性佳 | 查詢需 Join、插入多表 | 子類欄位多、差異大、存取頻率不同 |
| TABLE_PER_CLASS | @Inheritance(TABLE_PER_CLASS) | N | 完全隔離、無 Join | 多型查詢慢、Union 查詢 | 極少多型查詢、獨立演進 |
from Account):產生 Left Outer Join 所有子表,資料量大時效能下降若系統以「查詢特定帳戶類型」為主(如查活期、查儲蓄),JOINED 是最佳平衡;若頻繁執行「查所有帳戶」多型報表,SINGLE_TABLE 或加上快取機制較佳。