成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

詳解JPA 2.0動(dòng)態(tài)查詢機(jī)制:Criteria API

開(kāi)發(fā) 后端
JPA 2.0引入了 Criteria API,這個(gè) API 首次將類型安全查詢引入到 Java 應(yīng)用程序中,并為在運(yùn)行時(shí)動(dòng)態(tài)地構(gòu)造查詢提供一種機(jī)制。本文介紹如何使用 Criteria API 和與之密切相關(guān)的 Metamodel API 編寫動(dòng)態(tài)的類型安全查詢。

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

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

JPQL 查詢有什么缺陷?

JPA 1.0 引進(jìn)了 JPQL,這是一種強(qiáng)大的查詢語(yǔ)言,它在很大程度上導(dǎo)致了 JPA 的流行。不過(guò),基于字符串并使用有限語(yǔ)法的 JPQL 存在一些限制。要理解 JPQL 的主要限制之一,請(qǐng)查看清單 1 中的簡(jiǎn)單代碼片段,它通過(guò)執(zhí)行 JPQL 查詢選擇年齡大于 20 歲的 Person 列表:


清單 1. 一個(gè)簡(jiǎn)單(并且錯(cuò)誤)的 JPQL 查詢

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

 

這個(gè)基礎(chǔ)的例子顯示了 JPA 1.0 中的查詢執(zhí)行模型的以下關(guān)鍵方面:

  • JPQL 查詢被指定為一個(gè) String(第 2 行)。
  • EntityManager 是構(gòu)造一個(gè)包含給定 JPQL 字符串的可執(zhí)行 查詢實(shí)例的工廠(第 3 行)。
  • 查詢執(zhí)行的結(jié)果包含無(wú)類型的 java.util.List 的元素。

但是這個(gè)簡(jiǎn)單的例子有一個(gè)驗(yàn)證的錯(cuò)誤。該代碼能夠順利通過(guò)編譯,但將在運(yùn)行時(shí)失敗,因?yàn)樵?JPQL 查詢字符串的語(yǔ)法有誤。清單 1 的第 2 行的正確語(yǔ)法為:

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

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

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

Criteria API 的最大優(yōu)勢(shì)之一就是禁止構(gòu)造語(yǔ)法錯(cuò)誤的查詢。清單 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 行通過(guò)幾種可用方法之一獲取一個(gè) EntityManager 實(shí)例。
  • 在第 2 行,EntityManager 創(chuàng)建 QueryBuilder 的一個(gè)實(shí)例。QueryBuilderCriteriaQuery 的工廠。
  • 在第 3 行,QueryBuilder 工廠構(gòu)造一個(gè) CriteriaQuery 實(shí)例。CriteriaQuery 被賦予泛型類型。泛型參數(shù)聲明 CriteriaQuery 在執(zhí)行時(shí)返回的結(jié)果的類型。在構(gòu)造 CriteriaQuery 時(shí),您可以提供各種結(jié)果類型參數(shù) —— 從持久化實(shí)體(比如 Person.class)到形式更加靈活的 Object[]。
  • 第 4 行在 CriteriaQuery 實(shí)例上設(shè)置了查詢表達(dá)式。查詢表達(dá)式是在一個(gè)樹(shù)中組裝的核心單元或節(jié)點(diǎn),用于指定 CriteriaQuery。圖 1 顯示了在 Criteria API 中定義的查詢表達(dá)式的層次結(jié)構(gòu):

    圖 1. 查詢表達(dá)式中的接口層次結(jié)構(gòu)
    查詢表達(dá)式中的接口層次結(jié)構(gòu)

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

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

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

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

    第 5 行有更多學(xué)問(wèn)。注意 qb.gt() 方法的第一個(gè)輸入?yún)?shù):p.get(Person_.age),其中 p 是先前獲得的 Root< Person> 表達(dá)式。p.get(Person_.age) 是一個(gè)路徑表達(dá)式。路徑表達(dá)式是通過(guò)一個(gè)或多個(gè)持久化屬性從根表達(dá)式進(jìn)行導(dǎo)航得到的結(jié)果。因此,表達(dá)式 p.get(Person_.age) 表示使用 Personage 屬性從根表達(dá)式 p 導(dǎo)航。您可能不明白 Person_.age 是什么。您可以將其暫時(shí)看作一種表示 Personage 屬性的方法。我將在談?wù)?JPA 2.0 引入的新 Metamodel API 時(shí)詳細(xì)解釋 Person_.age。

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

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

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

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

    與對(duì)應(yīng)的無(wú)類型超接口相反:

    public interface Query {
    List getResultList();
    }

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

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

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

  • CriteriaQuery 是一個(gè)查詢表達(dá)式節(jié)點(diǎn)樹(shù)。在傳統(tǒng)的基于字符串的查詢語(yǔ)言中,這些表達(dá)式節(jié)點(diǎn)用于指定查詢子句,比如 FROM、WHEREORDER BY。圖 2 顯示了與查詢相關(guān)的子句:

    圖 2. CriteriaQuery 封裝了傳統(tǒng)查詢的子句
    查詢表達(dá)式的接口層次結(jié)構(gòu)

     
  • 查詢表達(dá)式被賦予泛型。一些典型的表達(dá)式是:
    • Root< T>,相當(dāng)于一個(gè) FROM 子句。
    • Predicate,其計(jì)算為布爾值 true 或 false(事實(shí)上,它被聲明為 interface Predicate extends Expression< Boolean>)。
    • Path< T>,表示從 Root< ?> 表達(dá)式導(dǎo)航到的持久化屬性。Root< T> 是一個(gè)沒(méi)有父類的特殊 Path< T>。
  • QueryBuilderCriteriaQuery 和各種查詢表達(dá)式的工廠。
  • CriteriaQuery 被傳遞給一個(gè)可執(zhí)行查詢并保留類型信息,這樣可以直接訪問(wèn)選擇列表的元素,而不需要任何運(yùn)行時(shí)強(qiáng)制類型轉(zhuǎn)換。

#p#

持久化域的元模型

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

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

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

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

 

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

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

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


清單 3. 一個(gè)簡(jiǎn)單的持久化實(shí)體

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

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

 

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

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


清單 4. 一個(gè)簡(jiǎn)單實(shí)體的規(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;
}

 

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

其他一些需要注意的要點(diǎn)包括:

  • 元模型 Person_.age 字段被聲明為類型 javax.persistence.metamodel.SingularAttributeSingularAttribute 是 JPA Metamodel API 中定義的接口之一,我將在下一小節(jié)描述它。SingularAttribute< Person, Integer> 的泛型參數(shù)表示該類聲明原來(lái)的持久化屬性和持久化屬性本身的類型。
  • 元模型類被注釋為 @StaticMetamodel(domain.Person.class) 以將其標(biāo)記為一個(gè)與原來(lái)的持久化 domain.Person 實(shí)體對(duì)應(yīng)的元模型類。

Metamodel API

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

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


圖 3. Metamodel API 中的持久化類型的接口的層次結(jié)構(gòu)
圖 3. Metamodel API 中的持久化類型的接口的層次結(jié)構(gòu)
 

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


圖 4. Metamodel API 中的持久化屬性的接口的層次結(jié)構(gòu)
圖 4. Metamodel API 中的持久化屬性的接口的層次結(jié)構(gòu)
 

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

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

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

運(yùn)行時(shí)作用域

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

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


圖 5. 元模型接口是持久化單元中的類型的容器
圖 5. 元模型接口是持久化單元中的類型的容器
 

這個(gè)接口允許通過(guò)元模型元素的對(duì)應(yīng)持久化實(shí)體類訪問(wèn)元模型元素。例如,要獲得對(duì) Person 持久化實(shí)體的持久化元數(shù)據(jù)的引用,可以編寫:

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

 

這是一個(gè)用類的名稱通過(guò) ClassLoader 獲得 Class 的類比:

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

 

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

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

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

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

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

[[6883]]  

注釋處理和元模型生成

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

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

$ javac domain/Person.java

 

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

編寫類型安全的查詢

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

函數(shù)表達(dá)式

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

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


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

				
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)建一個(gè) avg() 表達(dá)式,并將其用于查詢的 select() 子句。

[[6883]]  

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


清單 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";

 

符合謂詞

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


清單 7. CriteriaQuery 中的多值表達(dá)式

				
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"));

 

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

In< T> value(T value); 

 

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

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

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

 

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

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

 

連接關(guān)系

盡管 清單 6清單 7 中的例子將表達(dá)式用作構(gòu)建塊,查詢都是基于一個(gè)實(shí)體及其屬性之上的。但是查詢通常涉及到多個(gè)實(shí)體,這就要求您將多個(gè)實(shí)體連接 起來(lái)。CriteriaQuery 通過(guò)類型連接表達(dá)式 連接兩個(gè)實(shí)體。類型連接表達(dá)式有兩個(gè)類型參數(shù):連接源的類型和連接目標(biāo)屬性的可綁定類型。例如,如果您想查詢有一個(gè)或多個(gè) PurchaseOrder 沒(méi)有發(fā)出的 Customer,則需要通過(guò)一個(gè)表達(dá)式將 Customer 連接到 PurchaseOrder,其中 Customer 有一個(gè)名為 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);

 

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

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

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

 

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


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

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

 

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

使用參數(shù)

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

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

 

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

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

 

參數(shù)化查詢?cè)诓樵儓?zhí)行之前綁定參數(shù)的值:

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

 

在 JPQL 查詢中,查詢字符串中的參數(shù)以命名方式(前面帶有冒號(hào),例如 :age)或位置方式(前面帶有問(wèn)號(hào),例如 ?3)編碼。在 CriteriaQuery 中,參數(shù)本身就是查詢表達(dá)式。與其他表達(dá)式一樣,它們是強(qiáng)類型的,并且由表達(dá)式工廠(即 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ù)表達(dá)式被創(chuàng)建為帶有顯式類型信息 Integer,并且被直接用于將值 20 綁定到可執(zhí)行查詢。額外的類型信息對(duì)減少運(yùn)行時(shí)錯(cuò)誤十分有用,因?yàn)樽柚箙?shù)與包含不兼容類型的表達(dá)式比較,或阻止參數(shù)與不兼容類型的值綁定。JPQL 查詢的參數(shù)不能提供任何編譯時(shí)安全。

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

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

預(yù)測(cè)結(jié)果

您已經(jīng)看到 CriteriaQuery 在執(zhí)行時(shí)返回的結(jié)果已經(jīng)在 QueryBuilder 構(gòu)造 CriteriaQuery 時(shí)指定。查詢的結(jié)果被指定為一個(gè)或多個(gè)預(yù)測(cè)條件。可以通過(guò)兩種方式之一在 CriteriaQuery 接口上指定預(yù)測(cè)條件:

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

 

最簡(jiǎn)單并且最常用的預(yù)測(cè)條件是查詢候選類。它可以是隱式的,如清單 11 所示:


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

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

 

清單 11 中,來(lái)自 Account 的查詢沒(méi)有顯式地指定它的選擇條件,并且和顯式地選擇的候選類一樣。清單 12 顯示了一個(gè)使用顯式選擇條件的查詢:


清單 12. 使用單個(gè)顯式選擇條件的 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();

 

如果查詢的預(yù)測(cè)結(jié)果不是候選持久化實(shí)體本身,那么可以通過(guò)其他幾個(gè)構(gòu)造方法來(lái)生成查詢的結(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)建了一個(gè)由其他幾個(gè)可選擇的表達(dá)式組成的預(yù)測(cè)條件。construct() 方法創(chuàng)建給定類參數(shù)的一個(gè)實(shí)例,并使用來(lái)自輸入選擇條件的值調(diào)用一個(gè)構(gòu)造函數(shù)。例如,如果 CustomerDetails — 一個(gè)非持久化實(shí)體 — 有一個(gè)接受 Stringint 參數(shù)的構(gòu)造方法,那么 CriteriaQuery 可以通過(guò)從選擇的 Customer — 一個(gè)持久化實(shí)體 — 實(shí)例的名稱和年齡創(chuàng)建實(shí)例,從而返回 CustomerDetails 作為它的結(jié)果,如清單 14 所示:


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

				
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));

 

可以將多個(gè)預(yù)測(cè)條件合并在一起,以組成一個(gè)表示 Object[]Tuple 的復(fù)合條件。清單 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();

 

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

Tuple 是一個(gè)表示一行數(shù)據(jù)的 JPA 定義接口。從概念上看,Tuple 是一個(gè) TupleElement 列表 — 其中 TupleElement 是源自單元和所有查詢表達(dá)式的根。包含在 Tuple 中的值可以被基于 0 的整數(shù)索引訪問(wèn)(類似于熟悉的 JDBC 結(jié)果),也可以被 TupleElement 的別名訪問(wèn),或直接通過(guò) TupleElement 訪問(wèn)。清單 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);

[[6883]]  

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

這些能夠改變結(jié)果的方法的行為和在構(gòu)造期間被指定為 CriteriaQuery 的類型參數(shù)結(jié)果共同組成 multiselect() 方法的語(yǔ)義。這個(gè)方法根據(jù)最終實(shí)現(xiàn)結(jié)果的 CriteriaQuery 的結(jié)果類型解釋它的輸入條件。要像 清單 14 一樣使用 multiselect() 構(gòu)造 CustomerDetails 實(shí)例,您需要將 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));

 

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


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

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

 

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

#p#

高級(jí)特性

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

  • 使用弱類型的 API 構(gòu)建動(dòng)態(tài)查詢
  • 使用數(shù)據(jù)庫(kù)支持的函數(shù)作為查詢表達(dá)式來(lái)擴(kuò)展語(yǔ)法
  • 編輯查詢實(shí)現(xiàn) “在結(jié)果中搜索” 功能
  • 根據(jù)例子進(jìn)行查詢 — 數(shù)據(jù)庫(kù)社區(qū)熟悉的模式

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

Criteria API 的強(qiáng)類型檢查基于開(kāi)放期間的實(shí)例化元模型類的可用性。不過(guò),在某些情況下,選擇的實(shí)體僅能夠在運(yùn)行時(shí)決定。為了支持這種用法,Criteria API 方法提供一個(gè)并列版本,其中持久化屬性通過(guò)它們的名稱進(jìn)行引用(類似于 Java Reflection API),而不是引用實(shí)例化靜態(tài)元模型屬性。該 API 的這個(gè)并列版本可以通過(guò)犧牲編譯時(shí)類型檢查來(lái)真正地支持動(dòng)態(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)));

 

不過(guò),弱類型 API 不能夠返回正確的泛型表達(dá)式,因此生成一個(gè)編輯器來(lái)警告未檢查的轉(zhuǎn)換。一種消除這些煩人的警告消息的方法是使用 Java 泛型不常用的工具:參數(shù)化方法調(diào)用,比如 清單 19 中通過(guò)調(diào)用 get() 方法獲取路徑表達(dá)式。

可擴(kuò)展數(shù)據(jù)庫(kù)表達(dá)式

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

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

 

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


清單 20. 在 CriteriaQuery 中使用特定于數(shù)據(jù)庫(kù)的函數(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 中不能表達(dá)等效的查詢,因?yàn)樗恼Z(yǔ)法僅支持固定數(shù)量的表達(dá)式。動(dòng)態(tài) API 不受固定數(shù)量表達(dá)式的嚴(yán)格限制。

可編輯查詢

可以以編程的方式編輯 CriteriaQuery??梢愿淖儾樵兊淖泳?,比如它的選擇條件、WHERE 子句中的選擇謂詞和 ORDER BY 子句中的排序條件。可以在典型的 “在結(jié)果中搜索” 工具中使用這個(gè)編輯功能,以添加更多限制在后續(xù)步驟中進(jìn)一步細(xì)化查詢謂詞。

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


清單 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();

[[6883]]  

CriteriaQuery 上的 setter 方法 — select()、where()orderBy() — 使用新的參數(shù)替換先前的值。對(duì)應(yīng)的 getter 方法(比如 getOrderList())返回的列表不是活動(dòng)的,即在返回列表上添加或刪除元素不會(huì)導(dǎo)致修改 CriteriaQuery;另外,一些供應(yīng)商甚至返回不可變的列表以阻止意外使用。因此,良好的實(shí)踐是在添加和刪除新的表達(dá)式之前,將返回列表復(fù)制到一個(gè)新的列表中。

根據(jù)例子進(jìn)行查詢

動(dòng)態(tài)查詢 API 中的另一個(gè)有用特性就是它能夠輕松地支持根據(jù)例子進(jìn)行查詢。根據(jù)例子進(jìn)行查詢(由 IBM® Research 在 1970 年開(kāi)發(fā)出來(lái))通常被作為早期的軟件終端用戶可用性例子引用。根據(jù)例子進(jìn)行查詢的理念使用模板實(shí)例,而不是為查詢指定精確的謂詞。有了給定的模板實(shí)例之后,將創(chuàng)建一個(gè)聯(lián)合謂詞,其中每個(gè)謂詞都是模板實(shí)例的非 null 和非默認(rèn)屬性值。執(zhí)行該查詢將計(jì)算謂詞以查找所有與模板實(shí)例匹配的實(shí)例。根據(jù)例子進(jìn)行查詢?cè)紤]添加到 JPA 2.0 中,但最終沒(méi)有添加。OpenJPA 通過(guò)它的擴(kuò)展 OpenJPAQueryBuilder 接口支持這種查詢,如清單 22 所示:


清單 22. 使用 OpenJPA 的 CriteriaQuery 根據(jù)例子進(jìn)行查詢

				
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);

 

如這個(gè)例子所示,OpenJPA 的 QueryBuilder 接口擴(kuò)展支持以下表達(dá)式:

public < T> Predicate qbe(From< ?, T> from, T template);

 

這個(gè)表達(dá)式根據(jù)給定模板實(shí)例的屬性值生成一個(gè)聯(lián)合謂詞。例如,這個(gè)查詢將查詢所有薪水為 10000 評(píng)級(jí)為 1Employee。要進(jìn)一步控制比較,可以指定不用于比較的可選屬性,以及為值為 String 的屬性指定比較方式。

結(jié)束語(yǔ)

本文介紹了 JPA 2.0 中的新 Criteria API,它是一個(gè)用 Java 語(yǔ)言開(kāi)發(fā)動(dòng)態(tài)、類型安全的查詢的機(jī)制。CriteriaQuery 在運(yùn)行時(shí)被構(gòu)建為一個(gè)強(qiáng)類型查詢表達(dá)式樹(shù),本文通過(guò)一系列例子展示了它的用法。

本文還確立了 Metamodel API 的關(guān)鍵角色,并展示了實(shí)例化元模型類如何使編譯器能夠檢查查詢的正確性,從而避免語(yǔ)法有誤的 JPQL 查詢引起的運(yùn)行時(shí)錯(cuò)誤。除了保證語(yǔ)法正確之外,JPA 2.0 以編程的方式構(gòu)造查詢的特性還能通過(guò)數(shù)據(jù)庫(kù)函數(shù)實(shí)現(xiàn)更多強(qiáng)大的用途,比如通過(guò)例子進(jìn)行查詢。我希望本文的讀者能夠發(fā)現(xiàn)這些強(qiáng)大的新 API 的其他新用途。

責(zé)任編輯:yangsai 來(lái)源: IBMDW
相關(guān)推薦

2010-06-17 10:38:08

UML動(dòng)態(tài)建模機(jī)制

2009-09-24 13:03:38

Hibernate C

2009-06-30 16:46:45

Criteria進(jìn)階查

2009-06-30 16:44:10

Criteria基本查

2009-06-30 16:55:19

2009-06-17 14:17:40

Criteria條件查Hibernate

2010-01-22 09:52:32

JPA 2.0Java EE 6

2009-06-30 16:57:21

Criteria查詢

2020-01-16 11:23:32

Zookeeper數(shù)據(jù)結(jié)構(gòu)API

2009-06-01 14:54:50

jpaapiJava

2010-07-12 17:12:37

JPA 2.0緩存Hibernate緩存Java EE

2010-07-13 16:20:18

JPA 2.0緩存Hibernate緩存Java EE

2012-06-02 00:45:36

JPA查詢語(yǔ)言JPQL

2009-06-01 15:01:53

JPA實(shí)體狀態(tài)API

2012-06-02 00:50:29

JPA查詢語(yǔ)言JPQL

2012-06-05 02:20:24

JPAJava查詢語(yǔ)言

2009-07-08 15:01:00

Servlet Ses

2024-07-16 08:38:06

2021-09-06 18:55:57

MySQLCheckpoint機(jī)制

2012-01-09 11:26:15

Java
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 有码在线 | 久久久久久久av麻豆果冻 | 天天干国产 | 黄色网址免费看 | 国产精品爱久久久久久久 | 国产一区在线视频 | 91 久久| www.狠狠干| 国产精品一区二区三区免费观看 | 男女羞羞免费视频 | 涩涩视频网站在线观看 | 天天看天天爽 | 成人午夜免费视频 | 欧美亚洲视频在线观看 | 国产精品女人久久久 | 亚洲一区电影 | 日日摸天天添天天添破 | 国产精品久久久久久中文字 | 亚洲精品自在在线观看 | 国产精品成人一区二区三区 | 国产日韩欧美中文 | 欧美精品在欧美一区二区少妇 | 99精品观看 | 干干天天| 国产精品一区二区三区在线 | 在线成人av| 国产精品一区免费 | www.色.com | 久久久综合久久 | 一区二区视频在线观看 | 亚洲黄色高清视频 | 成人精品一区二区 | 在线欧美亚洲 | 97免费视频在线观看 | 亚洲精品乱码久久久久久蜜桃 | 91精品国产91久久久久久吃药 | 中文字幕二区 | 最新国产精品精品视频 | 日韩中文字幕一区二区 | 成人小视频在线观看 | 久久成人国产精品 |