首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

EJB 3 開發指南(10):SINGLE_TABLE 繼承策略 - 單表多型映射

     編輯:Java 相關知識

SINGLE_TABLE 是 JPA 預設繼承策略,將整個繼承階層映射到單一資料表,透過「鑑別欄位」區分子類型。優點是查詢無需 Join、效能最佳;缺點是子類獨有欄位會產生大量空值。本文示範完整映射配置與自動鑑別值賦予機制。

一、資料表結構:t_accounts

account_id (PK) | balance | account_type | interest_rate | overdraft_limit
  • account_type:鑑別欄位,'C'=CheckingAccount(活期)、'S'=SavingsAccount(儲蓄)
  • interest_rate:僅儲蓄帳戶有效,活期為 NULL
  • overdraft_limit:僅活期帳戶有效,儲蓄為 NULL

t_accounts 表結構與資料

二、父類:Account

@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...。

三、子類定義與自動鑑別值

活期帳戶:CheckingAccount

@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; }
}

儲蓄帳戶:SavingsAccount

@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。

五、查詢行為

JPQLSQL 對應結果類型
from AccountSELECT * FROM t_accounts混合 List<Account> (含 CheckingAccount、SavingsAccount 實例)
from CheckingAccountSELECT * FROM t_accounts WHERE account_type='C'List<CheckingAccount>
from SavingsAccountSELECT * FROM t_accounts WHERE account_type='S'List<SavingsAccount>

六、優缺點與選擇指南

優點缺點
查詢最快(無 Join)稀疏欄位產生大量 NULL,浪費儲存空間
插入/更新單表,簡單高效子類獨有欄位無法加 NOT NULL 約束
多型查詢自然支援表結構變更影響所有子類,耦合度高

適用場景

  • 繼承層級淺(1-2 層)、子類欄位少(<5 個)、查詢頻繁且多型查詢佔大多數
  • 原有系統重構,資料表已為單表結構

不適用場景

  • 子類擁有大量獨有欄位(如 20+ 欄位)
  • 需對子類獨有欄位加資料庫層級約束(NOT NULL、UNIQUE)
  • 子類獨立演進頻率高,結構變更頻繁

遷移提示

若未來需遷移至 JOINED 策略:

  1. 建立子表,從主表拆分欄位
  2. 修改 @Inheritance(JOINED)、加上 @PrimaryKeyJoinColumn
  3. 移除 @DiscriminatorColumn 與 insertable/updatable=false
  4. 資料遷移腳本分離歷史資料
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved