新聞中心
自從 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 使用,比如 EntityManagerFactory 或 EntityManager。
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)绾翁峁椭?/strong>
Criteria API 的最大優(yōu)勢(shì)之一就是禁止構(gòu)造語(yǔ)法錯(cuò)誤的查詢。清單 2 使用 CriteriaQuery 接口重新編寫(xiě)了 清單 1 中的 JPQL 查詢:
清單 2. 編寫(xiě) 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í)例。QueryBuilder是CriteriaQuery的工廠。 - 在第 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)
首先,將
CriteriaQuery設(shè)置為從Person.class查詢。結(jié)果返回Root< Person>實(shí)例p。Root是一個(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)表示使用Person的age屬性從根表達(dá)式p導(dǎo)航。您可能不明白Person_.age是什么。您可以將其暫時(shí)看作一種表示Person的age屬性的方法。我將在談?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、WHERE和ORDER BY。圖 2 顯示了與查詢相關(guān)的子句:圖 2.
CriteriaQuery封裝了傳統(tǒng)查詢的子句- 查詢表達(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>。
QueryBuilder是CriteriaQuery和各種查詢表達(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_.age 是 Person_ 類中的公共靜態(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 的類型可以確定 Person 的 age 屬性是一個(gè)數(shù)字字段,不能與 String 進(jìn)行比較。
其他一些需要注意的要點(diǎn)包括:
- 元模型
Person_.age字段被聲明為類型javax.persistence.metamodel.SingularAttribute。SingularAttribute是 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.Field 或 java.lang.reflect.Method )來(lái)描述 java.lang.Class 的組成一樣,JPA Metamodel API 也需要其他接口(比如 SingularAttribute 和 PluralAttribute)來(lái)描述元模型類的類型及其屬性。
圖 3 顯示了在 Metamodel API 中定義用于描述類型的接口:
圖 3. Metamodel API 中的持久化類型的接口的層次結(jié)構(gòu)
圖 4 顯示了在 Metamodel API 中定義用于描述屬性的接口:
圖 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、Entity 和 Embeddable。在 JPA 2.0 之前,這種語(yǔ)義區(qū)分是通過(guò)持久化類定義中的對(duì)應(yīng)類級(jí)別注釋來(lái)表示的。JPA Metamodel 在 javax.persistence.metamodel 包中描述了 3 個(gè)獨(dú)立的接口( MappedSuperclassType、EntityType 和 EmbeddableType ),以更加鮮明的對(duì)比它們的語(yǔ)義特征。類似地,可以通過(guò)接口(比如 SingularAttribute、CollectionAttribute 和 MapAttribute)在類型定義級(jí)別上區(qū)分持久化屬性。
除了方便描述之外,這些專門(mén)化的元模型接口還有實(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)接口與專門(mén)用于描述持久化元數(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é)果將收到可怕的 ClassNotFoundException 或 NoClassDef 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. 元模型接口是持久化單元中的類型的容器
這個(gè)接口允許通過(guò)元模型元素的對(duì)應(yīng)持久化實(shí)體類訪問(wèn)元模型元素。例如,要獲得對(duì) Person 持久化實(shí)體的持久化元數(shù)據(jù)的引用,可以編寫(xiě):
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)用程序也能夠編寫(xiě)這些元模型類。如果應(yīng)用程序開(kāi)發(fā)人員編寫(xiě)元模型類,這些類就稱為非規(guī)范元模型。現(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í)體,您將傾向于不親自編寫(xiě)元模型類,這是很自然的事情。持久化提供者應(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 所在的目錄,并且作為該編譯的一部分。
編寫(xiě)類型安全的查詢
到目前為止,我已經(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,它是專門(mén)針對(duì)類型被聲明為 java.util.Set 的屬性的 Join。類似地,對(duì)于其他受支持的多值持久化屬性類型,該 API 定義 CollectionJoin、ListJoin 和 MapJoin。(圖 1 顯示了各種連接表達(dá)式)。在 清單 8 的第 3 行不需要進(jìn)行顯式的轉(zhuǎn)換,因?yàn)?CriteriaQuery 和 Metamodel API 通過(guò)覆蓋 join() 的方法能夠識(shí)別和區(qū)分聲明為 java.util.Collection 或 List 或者 Set 或 Map 的屬性類型。
在查詢中使用連接在連接實(shí)體上形成一個(gè)謂詞。因此,如果您想要選擇有一個(gè)或多個(gè)未發(fā)送 PurchaseOrder 的 Customer,可以通過(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"; |
盡管編寫(xiě)查詢時(shí)通常包含常量文本值,但這不是一個(gè)良好實(shí)踐。良好實(shí)踐是參數(shù)化查詢,從而僅解析或準(zhǔn)備查詢一次,然后再緩存并重用它。因此,編寫(xiě)查詢的最好方法是使用命名參數(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è)接受 String 和 int 參數(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 重新編寫(xiě)了 清單 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??梢愿淖儾樵兊淖泳洌热缢倪x擇條件、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í)為 1 的 Employee。要進(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 的其他新用途。
本文標(biāo)題:詳解JPA2.0動(dòng)態(tài)查詢機(jī)制:CriteriaAPI
瀏覽路徑:http://www.5511xx.com/article/cogsied.html


咨詢
建站咨詢
