Object-Relational Metadata Mapping Patterns#

本章介紹三種利用元資料(metadata)來處理物件與關聯式資料庫對映的模式:Metadata MappingQuery ObjectRepository。它們透過集中管理對映資訊、抽象化查詢語言、以及提供集合式介面,進一步降低領域層與資料庫之間的耦合。

Metadata Mapping#

將物件-關聯式對映的細節儲存在元資料中。

意圖#

Metadata Mapping 將資料庫欄位與物件欄位之間的對映關係以元資料形式儲存,而非寫死在程式碼中。透過元資料驅動的方式,用通用程式碼處理載入、儲存等操作。

運作方式#

  • 元資料可儲存於 XML 檔案、程式碼中的資料結構、或資料庫本身
  • 使用 DataMap 物件持有某個領域類別的對映資訊,內含多個 ColumnMap 物件,每個 ColumnMap 描述一個欄位的對映(欄位名稱、資料庫欄位名稱、型別轉換等)
  • 兩種利用元資料的方式:
    • Code Generation(程式碼生成):在編譯期根據元資料產生 mapper 的原始碼。優點是生成的程式碼可除錯,效能與手寫相同
    • Reflective Programming(反射式程式設計):在執行期使用反射機制,根據元資料動態存取物件欄位。更靈活但效能較差,且除錯困難
  • 使用反射方式時,通用的 findloadinsertupdate 方法可以處理任何領域類別——只需要正確的元資料

書中範例以 Java 反射方式實作。DataMap 類別持有表名、領域類別、ColumnMap 列表。findObject 方法根據 ID 產生 SQL、執行查詢、透過反射設定物件欄位值。整個過程完全由元資料驅動,新增領域類別只需新增元資料定義,不需寫新的 mapper。

何時使用#

  • 適合:大型專案中有大量領域類別需要對映,手寫每個 mapper 的成本太高
  • Code Generation 適合:需要效能、需要可除錯的程式碼、且可以接受編譯步驟
  • Reflective Programming 適合:需要完全動態的對映、且效能不是首要考量
  • 大多數商用 O/R Mapping 工具都使用此模式作為核心機制
  • 自行實作需要可觀的投入,通常建議使用現有工具

Query Object#

代表資料庫查詢的物件。

意圖#

Query Object 是一個以 Interpreter 模式 建構的物件結構,能夠組合成 SQL 查詢。使用者以領域物件語言(類別名稱和欄位名稱)而非資料庫語言(表名和欄位名)來建構查詢。

運作方式#

  • Query Object 使用領域模型的術語來描述查詢條件,例如 query.addCriteria(Criteria.greaterThan("numberOfDependents", 0))
  • 透過 Metadata Mapping 將領域術語轉換為資料庫的表名和欄位名
  • 查詢的組成元素:Criteria(條件) 物件表示單一條件,多個 Criteria 組合成完整查詢
  • Query Object 在轉換為 SQL 時可以進行最佳化:
    • 消除冗餘查詢——若相同查詢已執行過,直接從 Identity Map 返回結果
    • 支援多資料庫方言——不同資料庫的 SQL 差異由 Query Object 的轉換邏輯處理
    • 支援多 schema——同一查詢可根據不同的 Metadata Mapping 產生不同的 SQL

Query Object 的核心價值在於讓領域層完全不需要知道資料庫的結構。開發者使用熟悉的領域概念(如「員工」、「扶養人數」)而非資料庫概念(如 employees 表、num_dependents 欄位)來描述查詢需求。

何時使用#

  • 適合:搭配 Metadata Mapping 使用,讓開發者以領域語言思考查詢
  • 適合:需要支援多種資料庫或多種 schema 的應用程式
  • 不適合:簡單的應用程式——直接撰寫 SQL 更快且更直觀
  • 通常不需要自行實作完整的 Query Object,商用 O/R Mapping 工具會提供此功能
  • 可與 Repository 搭配,讓 Repository 將 Query Object 轉換為實際的資料庫查詢

Repository#

在領域層與資料對映層之間進行中介,提供類似集合的介面來存取領域物件。

意圖#

Repository 將建構查詢的邏輯封裝起來,讓客戶端程式碼透過類似集合的介面來取得物件,完全不需要知道底層的資料來源實作。

運作方式#

  • 客戶端建立 Criteria(條件) 物件或使用 Specification 模式 描述查詢需求,提交給 Repository
  • Repository 內部將 criteria 轉換為適當的查詢(通常透過 Query ObjectMetadata Mapping
  • Repository 本身看起來像一個記憶體中的集合——支援 add、remove、find 等操作
  • 可以使用 Strategy 模式 在不同的資料存取策略之間切換:
    • RelationalStrategy:使用 SQL 查詢關聯式資料庫
    • InMemoryStrategy:在記憶體集合中搜尋,用於測試

Repository 的一大優勢在於可測試性。透過替換底層策略為記憶體實作(InMemoryStrategy),單元測試可以完全不依賴資料庫執行,大幅提升測試速度並簡化測試環境設定。

何時使用#

  • 適合:需要查詢的類型較多且需要靈活組合條件的情境。Repository 將查詢邏輯集中管理,避免在領域層中散布資料庫查詢
  • 適合:需要在多種資料來源之間切換(如測試時使用記憶體、生產時使用資料庫)
  • 不適合:只有少量固定查詢的簡單應用——直接使用幾個硬寫的 finder 方法更簡單
  • Repository 內部通常結合 Metadata MappingQuery Object 運作,三者共同構成一個強大的資料存取抽象層
  • 使用 Specification 模式 可以讓 criteria 的組合更具表達力——例如以 AND、OR、NOT 組合多個 specification