JPA2.0動態(tài)查詢機制CriteriaAPI怎么用

小編給大家分享一下JPA 2.0動態(tài)查詢機制Criteria API怎么用,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!

創(chuàng)新互聯(lián)主營政和網(wǎng)站建設的網(wǎng)絡公司,主營網(wǎng)站建設方案,手機APP定制開發(fā),政和h5小程序開發(fā)搭建,政和網(wǎng)站營銷推廣歡迎政和等地區(qū)企業(yè)咨詢

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

本文將介紹 Criteria API 和與之密切相關(guān)的 元模型(metamodel)概念。您將學習如何使用 Criteria API 開發(fā) Java 編譯器能夠檢查其正確性的查詢,從而減少運行時錯誤,這種查詢優(yōu)于傳統(tǒng)的基于字符串的 Java Persistence Query Language (JPQL) 查詢。借助使用數(shù)據(jù)庫函數(shù)或匹配模板實例的樣例查詢,我將演示編程式查詢構(gòu)造機制的強大威力,并將其與使用預定義語法的 JPQL 查詢進行對比。本文假設您具備基礎的 Java 語言編程知識,并了解常見的 JPA 使用,比如 EntityManagerFactoryEntityManager。

JPQL 查詢有什么缺陷?

JPA 1.0 引進了 JPQL,這是一種強大的查詢語言,它在很大程度上導致了 JPA 的流行。不過,基于字符串并使用有限語法的 JPQL 存在一些限制。要理解 JPQL 的主要限制之一,請查看清單 1 中的簡單代碼片段,它通過執(zhí)行 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 中的查詢執(zhí)行模型的以下關(guān)鍵方面:

  • JPQL 查詢被指定為一個 String(第 2 行)。

  • EntityManager 是構(gòu)造一個包含給定 JPQL 字符串的可執(zhí)行 查詢實例的工廠(第 3 行)。

  • 查詢執(zhí)行的結(jié)果包含無類型的 java.util.List 的元素。

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

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


不幸的是,Java 編譯器不能發(fā)現(xiàn)此類錯誤。在運行時,該錯誤將出現(xiàn)在第 3 或第 4 行(具體行數(shù)取決于 JPA 提供者是否在查詢構(gòu)造或執(zhí)行期間根據(jù) JPQL 語法解析 JPQL 字符串)。

類型安全查詢?nèi)绾翁峁椭?/strong>

Criteria API 的最大優(yōu)勢之一就是禁止構(gòu)造語法錯誤的查詢。清單 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 的核心構(gòu)造及其基本使用:

  • 第 1 行通過幾種可用方法之一獲取一個 EntityManager 實例。

  • 在第 2 行,EntityManager 創(chuàng)建 QueryBuilder 的一個實例。QueryBuilderCriteriaQuery 的工廠。

  • 在第 3 行,QueryBuilder 工廠構(gòu)造一個 CriteriaQuery 實例。CriteriaQuery 被賦予泛型類型。泛型參數(shù)聲明 CriteriaQuery 在執(zhí)行時返回的結(jié)果的類型。在構(gòu)造 CriteriaQuery 時,您可以提供各種結(jié)果類型參數(shù) —— 從持久化實體(比如 Person.class)到形式更加靈活的 Object[]。

  • 第 4 行在 CriteriaQuery 實例上設置了查詢表達式。查詢表達式是在一個樹中組裝的核心單元或節(jié)點,用于指定 CriteriaQuery。圖 1 顯示了在 Criteria API 中定義的查詢表達式的層次結(jié)構(gòu):
    圖 1. 查詢表達式中的接口層次結(jié)構(gòu)
    JPA 2.0動態(tài)查詢機制Criteria API怎么用

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

  • 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() 方法的第一個輸入?yún)?shù):p.get(Person_.age),其中 p 是先前獲得的 Root< Person> 表達式。p.get(Person_.age) 是一個路徑表達式。路徑表達式是通過一個或多個持久化屬性從根表達式進行導航得到的結(jié)果。因此,表達式 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) 的計算結(jié)果的類型為 Integer。由于 API 中的類型安全繼承,編輯器本身將對無意義的比較拋出錯誤,比如:

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

  • 第 6 行在 CriteriaQuery 上將謂詞設置為其 WHERE 子句。

  • 在第 7 行中,EntityManager 創(chuàng)建一個可執(zhí)行查詢,其輸入為 CriteriaQuery。這類似于構(gòu)造一個輸入為 JPQL 字符串的可執(zhí)行查詢。但是由于輸入 CriteriaQuery 包含更多的類型信息,所以得到的結(jié)果是 TypedQuery,它是熟悉的 javax.persistence.Query 的一個擴展。如其名所示,TypedQuery 知道執(zhí)行它返回的結(jié)果的類型。它是這樣定義的:

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

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

    public interface Query {
    List getResultList();
    }

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

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

現(xiàn)在歸納 清單 2 中的簡單例子的基本方面:

  • CriteriaQuery 是一個查詢表達式節(jié)點樹。在傳統(tǒng)的基于字符串的查詢語言中,這些表達式節(jié)點用于指定查詢子句,比如 FROM、WHEREORDER BY。圖 2 顯示了與查詢相關(guān)的子句:
    圖 2. CriteriaQuery 封裝了傳統(tǒng)查詢的子句
    JPA 2.0動態(tài)查詢機制Criteria API怎么用

  • 查詢表達式被賦予泛型。一些典型的表達式是:

    • Root< T>,相當于一個 FROM 子句。

    • Predicate,其計算為布爾值 true 或 false(事實上,它被聲明為 interface Predicate extends Expression< Boolean>)。

    • Path< T>,表示從 Root< ?> 表達式導航到的持久化屬性。Root< T> 是一個沒有父類的特殊 Path< T>。

  • QueryBuilderCriteriaQuery 和各種查詢表達式的工廠。

  • CriteriaQuery 被傳遞給一個可執(zhí)行查詢并保留類型信息,這樣可以直接訪問選擇列表的元素,而不需要任何運行時強制類型轉(zhuǎn)換。

持久化域的元模型

討論 清單 2 時指出了一個不常見的構(gòu)造:Person_.age,它表示 Person 的持久化屬性 age。清單 2 使用 Person_.age 形成一個路徑表達式,它通過 p.get(Person_.age)Root< Person> 表達式 p 導航而來。Person_.agePerson_ 類中的公共靜態(tài)字段,Person_靜態(tài)、已實例化的規(guī)范元模型類,對應于原來的 Person 實體類。

元模型類描述持久化類的元數(shù)據(jù)。如果一個類安裝 JPA 2.0 規(guī)范精確地描述持久化實體的元數(shù)據(jù),那么該元模型類就是規(guī)范的。規(guī)范的元模型類是靜態(tài)的,因此它的所有成員變量都被聲明為靜態(tài)的(也是 public 的)。Person_.age 是靜態(tài)成員變量之一。您可以在開發(fā)時在源代碼中生成一個具體的 Person_.java實例化 一個規(guī)范類。實例化之后,它就可以在編譯期間以強類型的方式引用 Person 的持久化屬性。

這個 Person_metamodel 類是引用 Person 的元信息的一種代替方法。這種方法類似于經(jīng)常使用(有人可能認為是濫用)的 Java Reflection API,但概念上有很大的不同。您可以使用反射獲得關(guān)于 java.lang.Class 的實例的元信息,但是不能以編譯器能夠檢查的方式引用關(guān)于 Person.class 的元信息。例如,使用反射時,您將這樣引用 Person.class中的 age 字段:

Field field = Person.class.getField("age");

不過,這種方法也存在很大的限制,類似于 清單 1 中基于字符串的 JPQL 查詢存在的限制。編譯器能夠順利編譯該代碼,但不能確定它是否可以正常工作。如果該代碼包含任何錯誤輸入,它在運行時肯定會失敗。反射不能實現(xiàn) JPA 2.0 的類型安全查詢 API 要實現(xiàn)的功能。

類型安全查詢 API 必須讓您的代碼能夠引用 Person 類中的持久化屬性 age,同時讓編譯器能夠在編譯期間檢查錯誤。JPA 2.0 提供的解決辦法通過靜態(tài)地公開相同的持久化屬性實例化名為 Person_ 的元模型類(對應于 Person)。

關(guān)于元信息的討論通常都是令人昏昏欲睡的。所以我將為熟悉的 Plain Old Java Object (POJO) 實體類展示一個具體的元模型類例子(domain.Person),如清單 3 所示:


清單 3. 一個簡單的持久化實體

package domain;
@Entitypublic class Person {
  @Id  private long ssn;  private string name;  private int age;

  // public gettter/setter methods  public String getName() {...}
}

這是 POJO 的典型定義,并且包含注釋(比如 @Entity@Id),從而讓 JPA 提供者能夠?qū)⑦@個類的實例作為持久化實體管理。

清單 4 顯示了 domain.Person 的對應靜態(tài)規(guī)范元模型類:


清單 4. 一個簡單實體的規(guī)范元模型

package domain;import javax.persistence.metamodel.SingularAttribute;

@javax.persistence.metamodel.StaticMetamodel(domain.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;
}

元模型類將原來的 domain.Person 實體的每個持久化屬性聲明為類型為 SingularAttribute< Person,?> 的靜態(tài)公共字段。通過利用這個 Person_元模型類,可以在編譯期間引用 domain.Person 的持久化屬性 age — 不是通過 Reflection API,而是直接引用靜態(tài)的 Person_.age 字段。然后,編譯器可以根據(jù) age 屬性聲明的類型實施類型檢查。我已經(jīng)列舉了一個關(guān)于此類限制的例子:QueryBuilder.gt(p.get(Person_.age), "xyz") 將導致編譯器錯誤,因為編譯器通過 QueryBuilder.gt(..) 的簽名和 Person_.age 的類型可以確定 Personage 屬性是一個數(shù)字字段,不能與 String 進行比較。

其他一些需要注意的要點包括:

  • 元模型 Person_.age 字段被聲明為類型 javax.persistence.metamodel.SingularAttribute。SingularAttribute 是 JPA Metamodel API 中定義的接口之一,我將在下一小節(jié)描述它。SingularAttribute< Person, Integer> 的泛型參數(shù)表示該類聲明原來的持久化屬性和持久化屬性本身的類型。

  • 元模型類被注釋為 @StaticMetamodel(domain.Person.class) 以將其標記為一個與原來的持久化 domain.Person 實體對應的元模型類。

Metamodel API

我將一個元模型類定義為一個持久化實體類的描述。就像 Reflection API 需要其他接口(比如 java.lang.reflect.Fieldjava.lang.reflect.Method)來描述 java.lang.Class 的組成一樣,JPA Metamodel API 也需要其他接口(比如 SingularAttributePluralAttribute)來描述元模型類的類型及其屬性。

圖 3 顯示了在 Metamodel API 中定義用于描述類型的接口:


圖 3. Metamodel API 中的持久化類型的接口的層次結(jié)構(gòu)
JPA 2.0動態(tài)查詢機制Criteria API怎么用

圖 4 顯示了在 Metamodel API 中定義用于描述屬性的接口:


圖 4. Metamodel API 中的持久化屬性的接口的層次結(jié)構(gòu)
JPA 2.0動態(tài)查詢機制Criteria API怎么用

JPA 的 Metamodel API 接口比 Java Reflection API 更加專業(yè)化。需要更細微的差別來表達關(guān)于持久化的豐富元信息。例如,Java Reflection API 將所有 Java 類型表示為 java.lang.Class。即沒有通過獨立的定義對概念進行區(qū)分,比如類、抽象類和接口。當然,您可以詢問 Class 它是一個接口還是一個抽象類,但這與通過兩個獨立的定義表示接口和抽象類的差別不同。

Java Reflection API 在 Java 語言誕生時就被引入(對于一種常見的多用途編程語言而言,這曾經(jīng)是一個非常前沿的概念),但是經(jīng)過多年的發(fā)展才認識到強類型系統(tǒng)的用途和強大之處。JPA Metamodel API 將強類型引入到持久化實體中。例如,持久化實體在語義上區(qū)分為 MappedSuperClassEntityEmbeddable。在 JPA 2.0 之前,這種語義區(qū)分是通過持久化類定義中的對應類級別注釋來表示的。JPA Metamodel 在 javax.persistence.metamodel 包中描述了 3 個獨立的接口( MappedSuperclassTypeEntityTypeEmbeddableType),以更加鮮明的對比它們的語義特征。類似地,可以通過接口(比如 SingularAttribute、CollectionAttributeMapAttribute)在類型定義級別上區(qū)分持久化屬性。

除了方便描述之外,這些專門化的元模型接口還有實用優(yōu)勢,能夠幫助構(gòu)建類型安全的查詢從而減少運行時錯誤。您在前面的例子中看到了一部分優(yōu)勢,隨著我通過 CriteriaQuery 描述關(guān)于連接的例子,您將看到更多優(yōu)勢。

運行時作用域

一般而言,可以將 Java Reflection API 的傳統(tǒng)接口與專門用于描述持久化元數(shù)據(jù)的 javax.persistence.metamodel 的接口進行比較。要進一步進行類比,則需要對元模型接口使用等效的運行時作用域概念。java.lang.Class 實例的作用域由 java.lang.ClassLoader 在運行時劃分。一組相互引用的 Java 類實例必須在 ClassLoader 作用域下定義。作用域的邊界是嚴格封閉 的,如果在 ClassLoader L 作用域下定義的類 A 試圖引用不在 ClassLoader L 作用域之內(nèi)的類 B,結(jié)果將收到可怕的 ClassNotFoundExceptionNoClassDef FoundError(對于處理包含多個 ClassLoader 的環(huán)境的開發(fā)人員或部署人員而言,問題就復雜了)。

現(xiàn)在將一組嚴格的可相互引用的類稱為運行時作用域,而在 JPA 1.0 中稱為持久化單元。持久化單元作用域的持久化實體在 META-INF/persistence.xml 文件的 < class> 子句中枚舉。在 JPA 2.0 中,通過 javax.persistence.metamodel.Metamodel 接口讓開發(fā)人員可以在運行時使用作用域。Metamodel 接口是特定持久化單元知道的所有持久化實體的容器,如圖 5 所示:


圖 5. 元模型接口是持久化單元中的類型的容器
JPA 2.0動態(tài)查詢機制Criteria API怎么用

這個接口允許通過元模型元素的對應持久化實體類訪問元模型元素。例如,要獲得對 Person 持久化實體的持久化元數(shù)據(jù)的引用,可以編寫:

EntityManagerFactory emf = ...;
Metamodel metamodel = emf.getMetamodel();
EntityType< Person> pClass = metamodel.entity(Person.class);

這是一個用類的名稱通過 ClassLoader 獲得 Class 的類比:

ClassLoader classloader =  Thread.currentThread().getContextClassLoader();
Class< ?> clazz = classloader.loadClass("domain.Person");

可以在運行時瀏覽 EntityType< Person> 獲得在 Person 實體中聲明的持久化屬性。如果應用程序在 pClass(比如 pClass.getSingularAttribute("age", Integer.class))上調(diào)用一個方法,它將返回一個 SingularAttribute< Person, Integer> 實例,該實例與實例化規(guī)范元模型類的靜態(tài) Person_.age 成員相同。最重要的是,對于應用程序可以通過 Metamodel API 在運行時引用的屬性,是通過實例化靜態(tài)規(guī)范元模型 Person_類向 Java 編譯器提供的。

除了將持久化實體分解為對應的元模型元素之外,Metamodel API 還允許訪問所有已知的元模型類 (Metamodel.getManagedTypes()),或者通過類的持久化信息訪問元模型類,例如 embeddable(Address.class),它將返回一個 EmbeddableType< Address> 實例(ManagedType< > 的子接口)。

在 JPA 中,關(guān)于 POJO 的元信息使用帶有源代碼注釋(或 XML 描述符)的持久化元信息進一步進行區(qū)分 —— 比如類是否是嵌入的,或者哪個字段用作主鍵。持久化元信息分為兩大類:持久化(比如 @Entity)和映射(比如 @Table)。在 JPA 2.0 中,元模型僅為持久化注釋(不是映射注釋)捕捉元數(shù)據(jù)。因此,使用當前版本的 Metamodel API 可以知道哪些字段是持久化的,但不能找到它們映射到的數(shù)據(jù)庫列。

規(guī)范和非規(guī)范

盡管 JPA 2.0 規(guī)范規(guī)定了規(guī)范的靜態(tài)元模型類的精確樣式(包括元模型類的完整限定名及其靜態(tài)字段的名稱),應用程序也能夠編寫這些元模型類。如果應用程序開發(fā)人員編寫元模型類,這些類就稱為非規(guī)范元模型?,F(xiàn)在,關(guān)于非規(guī)范元模型的規(guī)范還不是很詳細,因此對非規(guī)范元模型的支持不能在 JPA 提供者之間移植。您可能已經(jīng)注意到,公共靜態(tài)字段僅在規(guī)范元模型中聲明,而沒有初始化。聲明之后就可以在開發(fā) CriteriaQuery 時引用這些字段。但是,必須在運行時給它們賦值才有意義。盡管為規(guī)范元模型的字段賦值是 JPA 提供者的責任,但非規(guī)范元模型則不存在這一要求。使用非規(guī)范元模型的應用程序必須依賴于特定供應商機制,或開發(fā)自己的機制來在運行時初始化元模型屬性的字段值。

JPA 2.0動態(tài)查詢機制Criteria API怎么用 

注釋處理和元模型生成

如果您有許多持久化實體,您將傾向于不親自編寫元模型類,這是很自然的事情。持久化提供者應該 為您生成這些元模型類。在規(guī)范中沒有強制規(guī)定這種工具或生成機制,但是 JPA 之間已經(jīng)私下達成共識,他們將使用在 Java 6 編譯器中集成的 Annotation Processor 工具生成規(guī)范元模型。Apache OpenJPA 提供一個工具來生成這些元模型類,其生成方式有兩種,一是在您為持久化實體編譯源代碼時隱式地生成,二是通過顯式地調(diào)用腳本生成。在 Java 6 以前,有一個被廣泛使用的稱為 apt 的 Annotation Processor 工具,但在 Java 6 中,編譯器和 Annotation Processor 的合并被定義為標準的一部分。

要像持久化提供者一樣在 OpenJPA 中生成這些元模型類,僅需在編譯器的類路徑中使用 OpenJPA 類庫編譯 POJO 實體:

$ javac domain/Person.java

將生成規(guī)范元模型 Person_ 類,它將位于 Person.java 所在的目錄,并且作為該編譯的一部分。

編寫類型安全的查詢

到目前為止,我已經(jīng)構(gòu)建了 CriteriaQuery 的組件和相關(guān)的元模型類。現(xiàn)在,我將展示如何使用 Criteria API 開發(fā)一些查詢。

函數(shù)表達式

函數(shù)表達式將一個函數(shù)應用到一個或多個輸入?yún)?shù)以創(chuàng)建新的表達式。函數(shù)表達式的類型取決于函數(shù)的性質(zhì)及其參數(shù)的類型。輸入?yún)?shù)本身可以是表達式或文本值。編譯器的類型檢查規(guī)則與 API 簽名結(jié)合確定什么是合法輸入。

考慮一個對輸入表達式應用平均值的單參數(shù)表達式。CriteriaQuery 選擇所有 Account 的平均余額,如清單 5 所示:


清單 5. CriteriaQuery 中的函數(shù)表達式

CriteriaQuery< Double> c = cb.createQuery(Double.class);Root< Account> a = c.from(Account.class);

c.select(cb.avg(a.get(Account_.balance)));

等效的 JPQL 查詢?yōu)椋?/p>

String jpql = "select avg(a.balance) from Account a";

在 清單 5 中,QueryBuilder 工廠(由變量 cb 表示)創(chuàng)建一個 avg() 表達式,并將其用于查詢的 select() 子句。

JPA 2.0動態(tài)查詢機制Criteria API怎么用 

該查詢表達式是一個構(gòu)建塊,可以通過組裝它為查詢定義最后的選擇謂詞。清單 6 中的例子顯示了通過導航到 Account 的余額創(chuàng)建的 Path 表達式,然后 Path 表達式被用作兩個二進制函數(shù)表達式( greaterThan()lessThan())的輸入表達式,這兩個表達式的結(jié)果都是一個布爾表達式或一個謂詞。然后,通過 and() 操作合并謂詞以形成最終的選擇謂詞,查詢的 where() 子句將計算該謂詞:


清單 6. CriteriaQuery 中的 where() 謂詞

CriteriaQuery< Account> c = cb.createQuery(Account.class);Root< Account> account = c.from(Account.class);Path< Integer> balance = account.get(Account_.balance);
c.where(cb.and
       (cb.greaterThan(balance, 100), 
        cb.lessThan(balance), 200)));

等效的 JPQL 查詢?yōu)椋?/p>

"select a from Account a where a.balance>100 and a.balance< 200";

符合謂詞

某些表達式(比如 in())可以應用到多個表達式。清單 7 給出了一個例子:


清單 7. CriteriaQuery 中的多值表達式

CriteriaQuery< Account> c = cb.createQuery(Account.class);Root< Account> account = c.from(Account.class);Path< Person> owner = account.get(Account_.owner);Path< String> name = owner.get(Person_.name);
c.where(cb.in(name).value("X").value("Y").value("Z"));

這個例子通過兩個步驟從 Account 進行導航,創(chuàng)建一個表示帳戶所有者的名稱的路徑。然后,它創(chuàng)建一個使用路徑表達式作為輸入的 in() 表達式。in() 表達式計算它的輸入表達式是否等于它的參數(shù)之一。這些參數(shù)通過 value() 方法在 In< T> 表達式上指定,In< T> 的簽名如下所示:

In< T> value(T value);

注意如何使用 Java 泛型指定僅對值的類型為 T 的成員計算 In< T> 表達式。因為表示 Account 所有者的名稱的路徑表達式的類型為 String,所以與值為 String 類型的參數(shù)進行比較才有效,String 值參數(shù)可以是字面量或計算結(jié)果為 String 的另一個表達式。

將 清單 7 中的查詢與等效(正確)的 JPQL 進行比較:

"select a from Account a where a.owner.name in ('X','Y','Z')";

在 JPQL 中的輕微疏忽不僅不會被編輯器檢查到,它還可能導致意外結(jié)果。例如:

"select a from Account a where a.owner.name in (X, Y, Z)";

連接關(guān)系

盡管 清單 6 和 清單 7 中的例子將表達式用作構(gòu)建塊,查詢都是基于一個實體及其屬性之上的。但是查詢通常涉及到多個實體,這就要求您將多個實體連接 起來。CriteriaQuery 通過類型連接表達式 連接兩個實體。類型連接表達式有兩個類型參數(shù):連接源的類型和連接目標屬性的可綁定類型。例如,如果您想查詢有一個或多個 PurchaseOrder 沒有發(fā)出的 Customer,則需要通過一個表達式將 Customer 連接到 PurchaseOrder,其中 Customer 有一個名為 orders 類型為 java.util.Set< PurchaseOrder> 的持久化屬性,如清單 8 所示:


清單 8. 連接多值屬性

CriteriaQuery< Customer> q = cb.createQuery(Customer.class);Root< Customer> c = q.from(Customer.class);SetJoin< Customer, PurchaseOrder> o = c.join(Customer_.orders);

連接表達式從根表達式 c 創(chuàng)建,持久化屬性 Customer.orders 由連接源(Customer)和 Customer.orders 屬性的可綁定類型進行參數(shù)化,可綁定類型是 PurchaseOrder不是 已聲明的類型 java.util.Set< PurchaseOrder>。此外還要注意,因為初始屬性的類型為 java.util.Set,所以生成的連接表達式為 SetJoin,它是專門針對類型被聲明為 java.util.Set 的屬性的 Join。類似地,對于其他受支持的多值持久化屬性類型,該 API 定義 CollectionJoin、ListJoinMapJoin。(圖 1 顯示了各種連接表達式)。在 清單 8 的第 3 行不需要進行顯式的轉(zhuǎn)換,因為 CriteriaQuery 和 Metamodel API 通過覆蓋 join() 的方法能夠識別和區(qū)分聲明為 java.util.CollectionList 或者 SetMap 的屬性類型。

在查詢中使用連接在連接實體上形成一個謂詞。因此,如果您想要選擇有一個或多個未發(fā)送 PurchaseOrderCustomer,可以通過狀態(tài)屬性從連接表達式 o 進行導航,然后將其與 DELIVERED 狀態(tài)比較,并否定謂詞:

Predicate p = cb.equal(o.get(PurchaseOrder_.status), Status.DELIVERED)
        .negate();

創(chuàng)建連接表達式需要注意的一個地方是,每次連接一個表達式時,都會返回一個新的表達式,如清單 9 所示:


清單 9. 每次連接創(chuàng)建一個唯一的實例

SetJoin< Customer, PurchaseOrder> o1 = c.join(Customer_.orders);SetJoin< Customer, PurchaseOrder> o2 = c.join(Customer_.orders);assert o1 == o2;

清單 9 中對兩個來自相同表達式 c 的連接表達式的等同性斷言將失敗。因此,如果查詢的謂詞涉及到未發(fā)送并且值大于 $200 的 PurchaseOrder,那么正確的構(gòu)造是將 PurchaseOrder 與根 Customer 表達式連接起來(僅一次),把生成的連接表達式分配給本地變量(等效于 JPQL 中的范圍變量),并在構(gòu)成謂詞時使用本地變量。

使用參數(shù)

回顧一下本文初始的 JPQL 查詢(正確那個):

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

盡管編寫查詢時通常包含常量文本值,但這不是一個良好實踐。良好實踐是參數(shù)化查詢,從而僅解析或準備查詢一次,然后再緩存并重用它。因此,編寫查詢的最好方法是使用命名參數(shù):

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

參數(shù)化查詢在查詢執(zhí)行之前綁定參數(shù)的值:

Query query = em.createQuery(jpql).setParameter("age", 20);
List result = query.getResultList();

在 JPQL 查詢中,查詢字符串中的參數(shù)以命名方式(前面帶有冒號,例如 :age)或位置方式(前面帶有問號,例如 ?3)編碼。在 CriteriaQuery 中,參數(shù)本身就是查詢表達式。與其他表達式一樣,它們是強類型的,并且由表達式工廠(即 QueryBuilder)構(gòu)造。然后,可以參數(shù)化 清單 2 中的查詢,如清單 10 所示:


清單 10. 在 CriteriaQuery 中使用參數(shù)

ParameterExpression< Integer> age = qb.parameter(Integer.class);Predicate condition = qb.gt(p.get(Person_.age), age);
c.where(condition);TypedQuery< Person> q = em.createQuery(c); 
List< Person> result = q.setParameter(age, 20).getResultList();

比較該參數(shù)使用和 JPQL 中的參數(shù)使用:參數(shù)表達式被創(chuàng)建為帶有顯式類型信息 Integer,并且被直接用于將值 20 綁定到可執(zhí)行查詢。額外的類型信息對減少運行時錯誤十分有用,因為阻止參數(shù)與包含不兼容類型的表達式比較,或阻止參數(shù)與不兼容類型的值綁定。JPQL 查詢的參數(shù)不能提供任何編譯時安全。

清單 10 中的例子顯示了一個直接用于綁定的未命名表達式。還可以在構(gòu)造參數(shù)期間為參數(shù)分配第二個名稱。對于這種情況,您可以使用這個名稱將參數(shù)值綁定到查詢。不過,您不可以使用位置參數(shù)。線性 JPQL 查詢字符串中的整數(shù)位置有一定的意義,但是不能在概念模型為查詢表達式樹的 CriteriaQuery 上下文中使用整數(shù)位置。

JPA 查詢參數(shù)的另一個有趣方面是它們沒有內(nèi)部值。值綁定到可執(zhí)行查詢上下文中的參數(shù)。因此,可以合法地從相同的 CriteriaQuery 創(chuàng)建兩個獨立可執(zhí)行的查詢,并為這些可執(zhí)行查詢的相同參數(shù)綁定兩個整數(shù)值。

預測結(jié)果

您已經(jīng)看到 CriteriaQuery 在執(zhí)行時返回的結(jié)果已經(jīng)在 QueryBuilder 構(gòu)造 CriteriaQuery 時指定。查詢的結(jié)果被指定為一個或多個預測條件??梢酝ㄟ^兩種方式之一在 CriteriaQuery 接口上指定預測條件:

CriteriaQuery< T> select(Selection< ? extends T> selection);CriteriaQuery< T> multiselect(Selection< ?>... selections);

最簡單并且最常用的預測條件是查詢候選類。它可以是隱式的,如清單 11 所示:


清單 11. CriteriaQuery 默認選擇的候選區(qū)段

CriteriaQuery< Account> q = cb.createQuery(Account.class);Root< Account> account = q.from(Account.class);
List< Account> accounts = em.createQuery(q).getResultList();

在 清單 11 中,來自 Account 的查詢沒有顯式地指定它的選擇條件,并且和顯式地選擇的候選類一樣。清單 12 顯示了一個使用顯式選擇條件的查詢:


清單 12. 使用單個顯式選擇條件的 CriteriaQuery

CriteriaQuery< Account> q = cb.createQuery(Account.class);Root< Account> account = q.from(Account.class);
q.select(account);
List< Account> accounts = em.createQuery(q).getResultList();

如果查詢的預測結(jié)果不是候選持久化實體本身,那么可以通過其他幾個構(gòu)造方法來生成查詢的結(jié)果。這些構(gòu)造方法包含在 QueryBuilder 接口中,如清單 13 所示:


清單 13. 生成查詢結(jié)果的方法

< Y> CompoundSelection< Y> construct(Class< Y> result, Selection< ?>... terms);CompoundSelection< Object[]> array(Selection< ?>... terms);CompoundSelection< Tuple> tuple(Selection< ?>... terms);

清單 13 中的方法構(gòu)建了一個由其他幾個可選擇的表達式組成的預測條件。construct() 方法創(chuàng)建給定類參數(shù)的一個實例,并使用來自輸入選擇條件的值調(diào)用一個構(gòu)造函數(shù)。例如,如果 CustomerDetails — 一個非持久化實體 — 有一個接受 Stringint 參數(shù)的構(gòu)造方法,那么 CriteriaQuery 可以通過從選擇的 Customer — 一個持久化實體 — 實例的名稱和年齡創(chuàng)建實例,從而返回 CustomerDetails 作為它的結(jié)果,如清單 14 所示:


清單 14. 通過 construct() 將查詢結(jié)果包放入類的實例

CriteriaQuery< CustomerDetails> q = cb.createQuery(CustomerDetails.class);Root< Customer> c = q.from(Customer.class);
q.select(cb.construct(CustomerDetails.class,
              c.get(Customer_.name), c.get(Customer_.age));

可以將多個預測條件合并在一起,以組成一個表示 Object[]Tuple 的復合條件。清單 15 顯示了如何將結(jié)果包裝到 Object[] 中:


清單 15. 將結(jié)果包裝到 Object[]

CriteriaQuery< Object[]> q = cb.createQuery(Object[].class);Root< Customer> c = q.from(Customer.class);
q.select(cb.array(c.get(Customer_.name), c.get(Customer_.age));
List< Object[]> result = em.createQuery(q).getResultList();

這個查詢返回一個結(jié)果列表,它的每個元素都是一個長度為 2 的 Object[],第 0 個數(shù)組元素為 Customer 的名稱,第 1 個數(shù)組元素為 Customer 的年齡。

Tuple 是一個表示一行數(shù)據(jù)的 JPA 定義接口。從概念上看,Tuple 是一個 TupleElement 列表 — 其中 TupleElement 是源自單元和所有查詢表達式的根。包含在 Tuple 中的值可以被基于 0 的整數(shù)索引訪問(類似于熟悉的 JDBC 結(jié)果),也可以被 TupleElement 的別名訪問,或直接通過 TupleElement 訪問。清單 16 顯示了如何將結(jié)果包裝到 Tuple 中:


清單 16. 將查詢結(jié)果包裝到 Tuple

CriteriaQuery< Tuple> q = cb.createTupleQuery();Root< Customer> c = q.from(Customer.class);
TupleElement< String> tname = c.get(Customer_.name).alias("name");
q.select(cb.tuple(tname, c.get(Customer_.age).alias("age");
List< Tuple> result = em.createQuery(q).getResultList();
String name = result.get(0).get(name);
String age  = result.get(0).get(1);
JPA 2.0動態(tài)查詢機制Criteria API怎么用 

這個查詢返回一個結(jié)果列表,它的每個元素都是一個 Tuple。反過來,每個二元組都帶有兩個元素 — 可以被每個 TupleElement 的索引或別名(如果有的話)訪問,或直接被 TupleElement 訪問。清單 16 中需要注意的兩點是 alias() 的使用,它是將一個名稱綁定到查詢表達式的一種方式(創(chuàng)建一個新的副本),和 QueryBuilder 上的 createTupleQuery() 方法,它僅是 createQuery(Tuple.class) 的代替物。

這些能夠改變結(jié)果的方法的行為和在構(gòu)造期間被指定為 CriteriaQuery 的類型參數(shù)結(jié)果共同組成 multiselect() 方法的語義。這個方法根據(jù)最終實現(xiàn)結(jié)果的 CriteriaQuery 的結(jié)果類型解釋它的輸入條件。要像 清單 14 一樣使用 multiselect() 構(gòu)造 CustomerDetails 實例,您需要將 CriteriaQuery 的類型指定為 CustomerDetails,然后使用將組成 CustomerDetails 構(gòu)造方法的條件調(diào)用 multiselect(),如清單 17 所示:


清單 17. 基于結(jié)果類型的 multiselect() 解釋條件

CriteriaQuery< CustomerDetails> q = cb.createQuery(CustomerDetails.class);Root< Customer> c = q.from(Customer.class);
q.multiselect(c.get(Customer_.name), c.get(Customer_.age));

因為查詢結(jié)果類型為 CustomerDetails,multiselect() 將其預測條件解釋為 CustomerDetails 構(gòu)造方法參數(shù)。如將查詢指定為返回 Tuple,那么帶有相同參數(shù)的 multiselect() 方法將創(chuàng)建 Tuple 實例,如清單 18 所示:


清單 18. 使用 multiselect() 方法創(chuàng)建 Tuple 實例

CriteriaQuery< Tuple> q = cb.createTupleQuery();Root< Customer> c = q.from(Customer.class);
q.multiselect(c.get(Customer_.name), c.get(Customer_.age));

如果以 Object 作為結(jié)果類型或沒有指定類型參數(shù)時,multiselect() 的行為會變得更加有趣。在這些情況中,如果 multiselect() 使用單個輸入條件,那么返回值將為所選擇的條件。但是如果 multiselect() 包含多個輸入條件,結(jié)果將得到一個 Object[]。

高級特性

到目前為止,我主要強調(diào)了 Criteria API 的強類型,以及它如何幫助減少出現(xiàn)在基于字符串 JPQL 查詢中的語義錯誤。Criteria API 還是以編程的方式構(gòu)建查詢的機制,因此通常被稱為動態(tài) 查詢 API。編程式查詢構(gòu)造 API 的威力是無窮的,但它的利用還取決于用戶的創(chuàng)造能力。我將展示 4 個例子:

  • 使用弱類型的 API 構(gòu)建動態(tài)查詢

  • 使用數(shù)據(jù)庫支持的函數(shù)作為查詢表達式來擴展語法

  • 編輯查詢實現(xiàn) “在結(jié)果中搜索” 功能

  • 根據(jù)例子進行查詢 — 數(shù)據(jù)庫社區(qū)熟悉的模式

弱類型和動態(tài)查詢構(gòu)建

Criteria API 的強類型檢查基于開放期間的實例化元模型類的可用性。不過,在某些情況下,選擇的實體僅能夠在運行時決定。為了支持這種用法,Criteria API 方法提供一個并列版本,其中持久化屬性通過它們的名稱進行引用(類似于 Java Reflection API),而不是引用實例化靜態(tài)元模型屬性。該 API 的這個并列版本可以通過犧牲編譯時類型檢查來真正地支持動態(tài)查詢構(gòu)造。清單 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 不能夠返回正確的泛型表達式,因此生成一個編輯器來警告未檢查的轉(zhuǎn)換。一種消除這些煩人的警告消息的方法是使用 Java 泛型不常用的工具:參數(shù)化方法調(diào)用,比如 清單 19 中通過調(diào)用 get() 方法獲取路徑表達式。

可擴展數(shù)據(jù)庫表達式

動態(tài)查詢構(gòu)造機制的獨特優(yōu)勢是它的語法是可擴展的。例如,您可以在 QueryBuilder 接口中使用 function() 方法創(chuàng)建數(shù)據(jù)庫支持的表達式:

< T> Expression< T> function(String name, Class< T> type, Expression< ?>...args);

function() 方法創(chuàng)建一個帶有給定名稱和 0 個或多個輸入表達式的表達式。function() 表達式的計算結(jié)果為給定的類型。這允許應用程序創(chuàng)建一個計算數(shù)據(jù)庫的查詢。例如,MySQL 數(shù)據(jù)庫支持 CURRENT_USER() 函數(shù),它為服務器用于驗證當前客戶機的 MySQL 帳戶返回一個由用戶名和主機名組成的 UTF-8 字符串。應用程序可以在 CriteriaQuery 中使用未帶參數(shù)的 CURRENT_USER() 函數(shù),如清單 20 所示:


清單 20. 在 CriteriaQuery 中使用特定于數(shù)據(jù)庫的函數(shù)

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 中不能表達等效的查詢,因為它的語法僅支持固定數(shù)量的表達式。動態(tài) API 不受固定數(shù)量表達式的嚴格限制。

可編輯查詢

可以以編程的方式編輯 CriteriaQuery。可以改變查詢的子句,比如它的選擇條件、WHERE 子句中的選擇謂詞和 ORDER BY 子句中的排序條件??梢栽诘湫偷?“在結(jié)果中搜索” 工具中使用這個編輯功能,以添加更多限制在后續(xù)步驟中進一步細化查詢謂詞。

清單 21 中的例子創(chuàng)建了一個根據(jù)名稱對結(jié)果進行排序的查詢,然后編輯該查詢以根據(jù)郵政編碼進行查詢:


清單 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 editingList< 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();
JPA 2.0動態(tài)查詢機制Criteria API怎么用 

CriteriaQuery 上的 setter 方法 — select()、where()orderBy() — 使用新的參數(shù)替換先前的值。對應的 getter 方法(比如 getOrderList())返回的列表不是活動的,即在返回列表上添加或刪除元素不會導致修改 CriteriaQuery;另外,一

名稱欄目:JPA2.0動態(tài)查詢機制CriteriaAPI怎么用
網(wǎng)站網(wǎng)址:http://muchs.cn/article16/jpgdgg.html

成都網(wǎng)站建設公司_創(chuàng)新互聯(lián),為您提供軟件開發(fā)、企業(yè)網(wǎng)站制作、網(wǎng)站內(nèi)鏈、域名注冊、網(wǎng)站排名、做網(wǎng)站

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)