承接上篇,本文深入剖析 JPA 2.0 Criteria API 的核心基礎——靜態元模型與 Metamodel API,展示如何透過編譯期強類型檢查消除 JPQL 字串查詢的語法與語義錯誤,並詳細講解路徑導航、謂詞組合、連接、參數化與結果投影等實戰技巧。
JPQL 以字串構建查詢,編譯器無法驗證屬性名、類型匹配,執行期才拋出異常。JPA 2.0 引入 Canonical Metamodel(規範元模型):為每個實體類生成對應的 Entity_ 類(如 Person_),其中以 public static volatile SingularAttribute<Entity, Type> 形式暴露每個持久屬性。例如:
@StaticMetamodel(Person.class)
public class Person_ {
public static volatile SingularAttribute<Person, Long> ssn;
public static volatile SingularAttribute<Person, String> name;
public static volatile SingularAttribute<Person, Integer> age;
}
查詢時改用 p.get(Person_.age),編譯器即可檢查 age 是否存在、類型是否為 Integer,杜絕 qb.gt(p.get(Person_.age), "xyz") 這類低級錯誤。
類比 Reflection API 的 Class、Field,JPA 定義專用介面層級:
EntityType、EmbeddableType、MappedSuperclassType,語義區分實體、嵌入類、映射超類。SingularAttribute(單值)、CollectionAttribute、ListAttribute、SetAttribute、MapAttribute,精確表達集合語義。這些介面不僅供靜態元模型實作,亦支援執行期動態查詢:透過 EntityManagerFactory.getMetamodel() 獲取 Metamodel 容器,再以 metamodel.entity(Person.class) 取得 EntityType<Person>,進而動態導航屬性。
Metamodel 實例對應一個持久化單元(persistence.xml 定義的 <class> 列表),嚴格隔離不同單元的實體類型,避免跨單元類衝突,類比 ClassLoader 隔離類空間。
Entity_)、字段簽名固定,由 JPA 提供者(如 OpenJPA、Hibernate)透過 Annotation Processor(Java 6 javac -processor)自動生成,字段值由運行時注入。CriteriaQuery<Double> c = cb.createQuery(Double.class);
Root<Account> a = c.from(Account.class);
c.select(cb.avg(a.get(Account_.balance)));
Path<Integer> balance = account.get(Account_.balance);
c.where(cb.and(cb.greaterThan(balance, 100), cb.lessThan(balance, 200)));
in() 表達式Path<String> name = account.get(Account_.owner).get(Person_.name);
c.where(cb.in(name).value("X").value("Y").value("Z"));
SetJoin<Customer, PurchaseOrder> o = c.join(Customer_.orders);
Predicate p = cb.equal(o.get(PurchaseOrder_.status), Status.DELIVERED).negate();
注意:每次 join() 產生新實例,同一關聯需複用變數,否則生成笛卡兒積。
ParameterExpression<Integer> age = qb.parameter(Integer.class);
Predicate cond = qb.gt(p.get(Person_.age), age);
TypedQuery<Person> q = em.createQuery(c);
List<Person> r = q.setParameter(age, 20).getResultList();
參數表達式帶顯式類型,編譯期阻擋類型不匹配的綁定與比較,優於 JPQL 的命名/位置參數。
q.select(account) / 隱式預測:回傳實體列表。cb.construct(CustomerDetails.class, c.get(Customer_.name), c.get(Customer_.age)):構造非持久化 DTO。cb.array(...) / cb.tuple(...):回傳 Object[] 或 Tuple,支援索引、別名、TupleElement 多種存取方式。q.multiselect(...):根據 CriteriaQuery 泛型自動推導構造方式(DTO、Tuple、陣列)。下一篇將探討動態弱類型查詢、資料庫專屬函數擴展、可編輯查詢與 Query-by-Example 等進階應用。