高級特性
到目前為止,我主要強調了 Criteria API 的強類型,以及它如何幫助減少出現在基於字符串 JPQL 查詢中的語義錯誤。Criteria API 還是以編程的方式構建查詢的機制,因此通常被稱為動態 查詢 API。編程式查詢構造 API 的威力是無窮的,但它的利用還取決於用戶的創造能力。我將展示 4 個例子:
弱類型和動態查詢構建
Criteria API 的強類型檢查基於開放期間的實例化元模型類的可用性。不過,在某些情況下,選擇的實體僅能夠在運行時決定。為了支持這種用法,Criteria API 方法提供一個並列版本,其中持久化屬性通過它們的名稱進行引用(類似於 Java Reflection API),而不是引用實例化靜態元模型屬性。該 API 的這個並列版本可以通過犧牲編譯時類型檢查來真正地支持動態查詢構造。清單 19 使用弱類型 API 重新編寫了 清單 6 中的代碼:
清單 19. 弱類型查詢
Class< Account> cls =Class.forName("domain.Account"); Metamodel model = em.getMetamodel(); EntityType< Account> entity = model.entity(cls); CriteriaQuery< Account> c = cb.createQuery(cls); Root< Account> account = c.from(entity); Path< Integer> balance = account.< Integer>get("balance"); c.where(cb.and (cb.greaterThan(balance, 100), cb.lessThan(balance), 200)));
不過,弱類型 API 不能夠返回正確的泛型表達式,因此生成一個編輯器來警告未檢查的轉換。一種消除這些煩人的警告消息的方法是使用 Java 泛型不常用的工具:參數化方法調用,比如 清單 19 中通過調用 get()
方法獲取路徑表達式。
可擴展數據庫表達式
動態查詢構造機制的獨特優勢是它的語法是可擴展的。例如,您可以在 QueryBuilder
接口中使用 function()
方法創建數據庫支持的表達式:
< T> Expression< T> function(String name, Class< T> type, Expression< ?>...args);
function()
方法創建一個帶有給定名稱和 0 個或多個輸入表達式的表達式。function()
表達式的計算結果為給定的類型。這允許應用程序創建一個計算數據庫的查詢。例如,MySQL 數據庫支持 CURRENT_USER()
函數,它為服務器用於驗證當前客戶機的 MySQL 帳戶返回一個由用戶名和主機名組成的 UTF-8 字符串。應用程序可以在 CriteriaQuery
中使用未帶參數的 CURRENT_USER()
函數,如清單 20 所示:
清單 20. 在 CriteriaQuery
中使用特定於數據庫的函數
CriteriaQuery< Tuple> q = cb.createTupleQuery(); Root< Customer> c = q.from(Customer.class); Expression< String> currentUser = cb.function("CURRENT_USER", String.class, (Expression< ?>[])null); q.multiselect(currentUser, c.get(Customer_.balanceOwed));
注意,在 JPQL 中不能表達等效的查詢,因為它的語法僅支持固定數量的表達式。動態 API 不受固定數量表達式的嚴格限制。
可編輯查詢
可以以編程的方式編輯 CriteriaQuery
。可以改變查詢的子句,比如它的選擇條件、WHERE
子句中的選擇謂詞和 ORDER BY
子句中的排序條件。可以在典型的 “在結果中搜索” 工具中使用這個編輯功能,以添加更多限制在後續步驟中進一步細化查詢謂詞。
清單 21 中的例子創建了一個根據名稱對結果進行排序的查詢,然後編輯該查詢以根據郵政編碼進行查詢:
清單 21. 編輯 CriteriaQuery
CriteriaQuery< Person> c = cb.createQuery(Person.class); Root< Person> p = c.from(Person.class); c.orderBy(cb.asc(p.get(Person_.name))); List< Person> result = em.createQuery(c).getResultList(); // start editing List< Order> orders = c.getOrderList(); List< Order> newOrders = new ArrayList< Order>(orders); newOrders.add(cb.desc(p.get(Person_.zipcode))); c.orderBy(neWorders); List< Person> result2 = em.createQuery(c).getResultList();
在 CriteriaQuery
上的 setter 方法 — select()
、where()
或 orderBy()
— 使用新的參數替換先前的值。對應的 getter 方法(比如 getOrderList()
)返回的列表不是活動的,即在返回列表上添加或刪除元素不會導致修改 CriteriaQuery
;另外,一些供應商甚至返回不可變的列表以阻止意外使用。因此,良好的實踐是在添加和刪除新的表達式之前,將返回列表復制到一個新的列表中。
根據例子進行查詢
動態查詢 API 中的另一個有用特性就是它能夠輕松地支持根據例子進行查詢。根據例子進行查詢(由 IBM® Research 在 1970 年開發出來)通常被作為早期的軟件終端用戶可用性例子引用。根據例子進行查詢的理念使用模板實例,而不是為查詢指定精確的謂詞。有了給定的模板實例之後,將創建一個聯合謂詞,其中每個謂詞都是模板實例的非 null 和非默認屬性值。執行該查詢將計算謂詞以查找所有與模板實例匹配的實例。根據例子進行查詢曾考慮添加到 JPA 2.0 中,但最終沒有添加。OpenJPA 通過它的擴展 OpenJPAQueryBuilder
接口支持這種查詢,如清單 22 所示:
清單 22. 使用 OpenJPA 的 CriteriaQuery
根據例子進行查詢
CriteriaQuery< Employee> q = cb.createQuery(Employee.class); Employee example = new Employee(); example.setSalary(10000); example.setRating(1); q.where(cb.qbe(q.from(Employee.class), example);
如這個例子所示,OpenJPA 的 QueryBuilder
接口擴展支持以下表達式:
public < T> Predicate qbe(From< ?, T> from, T template);
這個表達式根據給定模板實例的屬性值生成一個聯合謂詞。例如,這個查詢將查詢所有薪水為 10000
評級為 1
的 Employee
。要進一步控制比較,可以指定不用於比較的可選屬性,以及為值為 String
的屬性指定比較方式。
結束語
本文介紹了 JPA 2.0 中的新 Criteria API,它是一個用 Java 語言開發動態、類型安全的查詢的機制。CriteriaQuery
在運行時被構建為一個強類型查詢表達式樹,本文通過一系列例子展示了它的用法。
本文還確立了 Metamodel API 的關鍵角色,並展示了實例化元模型類如何使編譯器能夠檢查查詢的正確性,從而避免語法有誤的 JPQL 查詢引起的運行時錯誤。除了保證語法正確之外,JPA 2.0 以編程的方式構造查詢的特性還能通過數據庫函數實現更多強大的用途,比如通過例子進行查詢。我希望本文的讀者能夠發現這些強大的新 API 的其他新用途。