程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> J2EE >> 詳解JPA 2.0動態查詢機制:Criteria API(1)

詳解JPA 2.0動態查詢機制:Criteria API(1)

編輯:J2EE

自從 JPA 於 2006 年首次被引入之後,它就得到了 Java 開發社區的廣泛支持。該規范的下一個主要更新 —— 2.0 版本 (JSR 317) —— 將在 2009 年年底完成。JPA 2.0 引入的關鍵特性之一就是 Criteria API,它為 Java 語言帶來了一種獨特的能力:開發一種 Java 編譯器可以在運行時驗證其正確性的查詢。Criteria API 還提供一個能夠在運行時動態地構建查詢的機制。

  • Spring+JPA,下一個人氣組合?
  • JPA與Hibernate的優缺點
  • Java開發三劍客JSF2.0、EJB3.1、JPA2.0現
  • JPA是什麼和Java EE對象持久化標准淺析
  • JPA規范標准及優勢淺析
本文將介紹 Criteria API 和與之密切相關的 元模型(metamodel)概念。您將學習如何使用 Criteria API 開發 Java 編譯器能夠檢查其正確性的查詢,從而減少運行時錯誤,這種查詢優於傳統的基於字符串的 Java Persistence Query Language (JPQL) 查詢。借助使用數據庫函數或匹配模板實例的樣例查詢,我將演示編程式查詢構造機制的強大威力,並將其與使用預定義語法的 JPQL 查詢進行對比。本文假設您具備基礎的 Java 語言編程知識,並了解常見的 JPA 使用,比如 EntityManagerFactoryEntityManager

JPQL 查詢有什麼缺陷?

JPA 1.0 引進了 JPQL,這是一種強大的查詢語言,它在很大程度上導致了 JPA 的流行。不過,基於字符串並使用有限語法的 JPQL 存在一些限制。要理解 JPQL 的主要限制之一,請查看清單 1 中的簡單代碼片段,它通過執行 JPQL 查詢選擇年齡大於 20 歲的 Person 列表:


清單 1. 一個簡單(並且錯誤)的 JPQL 查詢

EntityManager em = ...;
String jpql = "select p from Person where p.age > 20";
Query query = em.createQuery(jpql);
List result = query.getResultList();

這個基礎的例子顯示了 JPA 1.0 中的查詢執行模型的以下關鍵方面:

  • JPQL 查詢被指定為一個 String(第 2 行)。
  • EntityManager 是構造一個包含給定 JPQL 字符串的可執行 查詢實例的工廠(第 3 行)。
  • 查詢執行的結果包含無類型的 Java.util.List 的元素。

但是這個簡單的例子有一個驗證的錯誤。該代碼能夠順利通過編譯,但將在運行時失敗,因為該 JPQL 查詢字符串的語法有誤。清單 1 的第 2 行的正確語法為:

String jpql = "select p from Person p where p.age > 20";

不幸的是,Java 編譯器不能發現此類錯誤。在運行時,該錯誤將出現在第 3 或第 4 行(具體行數取決於 JPA 提供者是否在查詢構造或執行期間根據 JPQL 語法解析 JPQL 字符串)。

類型安全查詢如何提供幫助?

Criteria API 的最大優勢之一就是禁止構造語法錯誤的查詢。清單 2 使用 CriteriaQuery 接口重新編寫了 清單 1 中的 JPQL 查詢:


清單 2. 編寫 CriteriaQuery 的基本步驟

EntityManager em = ...
QueryBuilder qb = em.getQueryBuilder();
CriteriaQuery< Person> c = qb.createQuery(Person.class);
Root< Person> p = c.from(Person.class);
Predicate condition = qb.gt(p.get(Person_.age), 20);
c.where(condition);
TypedQuery< Person> q = em.createQuery(c); 
List< Person> result = q.getResultList();

清單 2 展示了 Criteria API 的核心構造及其基本使用:

  • 第 1 行通過幾種可用方法之一獲取一個 EntityManager 實例。
  • 在第 2 行,EntityManager 創建 QueryBuilder 的一個實例。QueryBuilderCriteriaQuery 的工廠。
  • 在第 3 行,QueryBuilder 工廠構造一個 CriteriaQuery 實例。CriteriaQuery 被賦予泛型類型。泛型參數聲明 CriteriaQuery 在執行時返回的結果的類型。在構造 CriteriaQuery 時,您可以提供各種結果類型參數 —— 從持久化實體(比如 Person.class)到形式更加靈活的 Object[]
  • 第 4 行在 CriteriaQuery 實例上設置了查詢表達式。查詢表達式是在一個樹中組裝的核心單元或節點,用於指定 CriteriaQuery。圖 1 顯示了在 Criteria API 中定義的查詢表達式的層次結構:
    圖 1. 查詢表達式中的接口層次結構
    查詢表達式中的接口層次結構

    首先,將 CriteriaQuery 設置為 Person.class 查詢。結果返回 Root< Person> 實例 pRoot 是一個查詢表達式,它表示持久化實體的范圍。Root< T> 實際上表示:“對所有類型為 T 的實例計算這個查詢。” 這類似於 JPQL 或 SQL 查詢的 FROM 子句。另外還需要注意,Root< Person> 是泛型的(實際上每個表達式都是泛型的)。類型參數就是表達式要計算的值的類型。因此 Root< Person> 表示一個對 Person.class 進行計算的表達式。第 5 行構造一個 PredicatePredicate 是計算結果為 true 或 false 的常見查詢表達式形式。謂詞由 QueryBuilder 構造,QueryBuilder 不僅是 CriteriaQuery 的工廠,同時也是查詢表達式的工廠。QueryBuilder 包含構造傳統 JPQL 語法支持的所有查詢表達式的 API 方法,並且還包含額外的方法。在 清單 2 中,QueryBuilder 用於構造一個表達式,它將計算第一個表達式參數的值是否大於第二個參數的值。方法簽名為:

  • Predicate gt(Expression< ? extends Number> x, Number y);               
    

    這個方法簽名是展示使用強類型語言(比如 Java)定義能夠檢查正確性並阻止錯誤的 API 的好例子。該方法簽名指定,僅能將值為 Number 的表達式與另一個值也為 Number 的表達式進行比較(例如,不能與值為 String 的表達式進行比較):

    Predicate condition = qb.gt(p.get(Person_.age), 20);
    

    第 5 行有更多學問。注意 qb.gt() 方法的第一個輸入參數:p.get(Person_.age),其中 p 是先前獲得的 Root< Person> 表達式。p.get(Person_.age) 是一個路徑表達式。路徑表達式是通過一個或多個持久化屬性從根表達式進行導航得到的結果。因此,表達式 p.get(Person_.age) 表示使用 Personage 屬性從根表達式 p 導航。您可能不明白 Person_.age 是什麼。您可以將其暫時看作一種表示 Personage 屬性的方法。我將在談論 JPA 2.0 引入的新 Metamodel API 時詳細解釋 Person_.age

    如前所述,每個查詢表達式都是泛型的,以表示表達式計算的值的類型。如果 Person.class 中的 age 屬性被聲明為類型 Integer (或 int),則表達式 p.get(Person_.age) 的計算結果的類型為 Integer。由於 API 中的類型安全繼承,編輯器本身將對無意義的比較拋出錯誤,比如:

    Predicate condition = qb.gt(p.get(Person_.age, "xyz"));

  • 第 6 行在 CriteriaQuery 上將謂詞設置為其 WHERE 子句。
  • 在第 7 行中,EntityManager 創建一個可執行查詢,其輸入為 CriteriaQuery。這類似於構造一個輸入為 JPQL 字符串的可執行查詢。但是由於輸入 CriteriaQuery 包含更多的類型信息,所以得到的結果是 TypedQuery,它是熟悉的 Javax.persistence.Query 的一個擴展。如其名所示,TypedQuery 知道執行它返回的結果的類型。它是這樣定義的:

    public interface TypedQuery< T> extends Query {
                 List< T> getResultList();
    }
    

    與對應的無類型超接口相反:

    public interface Query {
    List getResultList();
    }

    很明顯,TypedQuery 結果具有相同的 Person.class 類型,該類型在構造輸入 CriteriaQuery 時由 QueryBuilder 指定(第 3 行)。

  • 在第 8 行中,當最終執行查詢以獲得結果列表時,攜帶的類型信息展示了其優勢。得到的結果是帶有類型的 Person 列表,從而使開發人員在遍歷生成的元素時省去麻煩的強制類型轉換(同時減少了 ClassCastException 運行時錯誤)。

現在歸納 清單 2 中的簡單例子的基本方面:

  • CriteriaQuery 是一個查詢表達式節點樹。在傳統的基於字符串的查詢語言中,這些表達式節點用於指定查詢子句,比如 FROMWHEREORDER BY。圖 2 顯示了與查詢相關的子句:
    圖 2. CriteriaQuery 封裝了傳統查詢的子句
    查詢表達式的接口層次結構
  • 查詢表達式被賦予泛型。一些典型的表達式是:
    • Root< T>,相當於一個 FROM 子句。
    • Predicate,其計算為布爾值 true 或 false(事實上,它被聲明為 interface Predicate extends Expression< Boolean>)。
    • Path< T>,表示從 Root< ?> 表達式導航到的持久化屬性。Root< T> 是一個沒有父類的特殊 Path< T>
  • QueryBuilderCriteriaQuery 和各種查詢表達式的工廠。
  • CriteriaQuery 被傳遞給一個可執行查詢並保留類型信息,這樣可以直接訪問選擇列表的元素,而不需要任何運行時強制類型轉換。
  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved