小編給大家分享一下JPA 2.0動(dòng)態(tài)查詢機(jī)制Criteria API怎么用,相信大部分人都還不怎么了解,因此分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后大有收獲,下面讓我們一起去了解一下吧!
創(chuàng)新互聯(lián)主營(yíng)政和網(wǎng)站建設(shè)的網(wǎng)絡(luò)公司,主營(yíng)網(wǎng)站建設(shè)方案,手機(jī)APP定制開發(fā),政和h5小程序開發(fā)搭建,政和網(wǎng)站營(yíng)銷推廣歡迎政和等地區(qū)企業(yè)咨詢
自從 JPA 于 2006 年首次被引入之后,它就得到了 Java 開發(fā)社區(qū)的廣泛支持。該規(guī)范的下一個(gè)主要更新 —— 2.0 版本 (JSR 317) —— 將在 2009 年年底完成。JPA 2.0 引入的關(guān)鍵特性之一就是 Criteria API,它為 Java 語言帶來了一種獨(dú)特的能力:開發(fā)一種 Java 編譯器可以在運(yùn)行時(shí)驗(yàn)證其正確性的查詢。Criteria API 還提供一個(gè)能夠在運(yùn)行時(shí)動(dòng)態(tài)地構(gòu)建查詢的機(jī)制。
本文將介紹 Criteria API 和與之密切相關(guān)的 元模型(metamodel)概念。您將學(xué)習(xí)如何使用 Criteria API 開發(fā) Java 編譯器能夠檢查其正確性的查詢,從而減少運(yùn)行時(shí)錯(cuò)誤,這種查詢優(yōu)于傳統(tǒng)的基于字符串的 Java Persistence Query Language (JPQL) 查詢。借助使用數(shù)據(jù)庫函數(shù)或匹配模板實(shí)例的樣例查詢,我將演示編程式查詢構(gòu)造機(jī)制的強(qiáng)大威力,并將其與使用預(yù)定義語法的 JPQL 查詢進(jìn)行對(duì)比。本文假設(shè)您具備基礎(chǔ)的 Java 語言編程知識(shí),并了解常見的 JPA 使用,比如 EntityManagerFactory
或 EntityManager
。
JPQL 查詢有什么缺陷?
JPA 1.0 引進(jìn)了 JPQL,這是一種強(qiáng)大的查詢語言,它在很大程度上導(dǎo)致了 JPA 的流行。不過,基于字符串并使用有限語法的 JPQL 存在一些限制。要理解 JPQL 的主要限制之一,請(qǐng)查看清單 1 中的簡(jiǎn)單代碼片段,它通過執(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é)果包含無類型的 java.util.List
的元素。
但是這個(gè)簡(jiǎn)單的例子有一個(gè)驗(yàn)證的錯(cuò)誤。該代碼能夠順利通過編譯,但將在運(yùn)行時(shí)失敗,因?yàn)樵?JPQL 查詢字符串的語法有誤。清單 1 的第 2 行的正確語法為:
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 語法解析 JPQL 字符串)。
類型安全查詢?nèi)绾翁峁椭?/strong>
Criteria API 的最大優(yōu)勢(shì)之一就是禁止構(gòu)造語法錯(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 行通過幾種可用方法之一獲取一個(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è)樹中組裝的核心單元或節(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 的常見查詢表達(dá)式形式。謂詞由 QueryBuilder
構(gòu)造,QueryBuilder
不僅是 CriteriaQuery
的工廠,同時(shí)也是查詢表達(dá)式的工廠。QueryBuilder
包含構(gòu)造傳統(tǒng) JPQL 語法支持的所有查詢表達(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)類型語言(比如 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é)問。注意 qb.gt()
方法的第一個(gè)輸入?yún)?shù):p.get(Person_.age)
,其中 p
是先前獲得的 Root< Person>
表達(dá)式。p.get(Person_.age)
是一個(gè)路徑表達(dá)式。路徑表達(dá)式是通過一個(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ì)無意義的比較拋出錯(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)的無類型超接口相反:
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
列表,從而使開發(fā)人員在遍歷生成的元素時(shí)省去麻煩的強(qiáng)制類型轉(zhuǎn)換(同時(shí)減少了 ClassCastException
運(yùn)行時(shí)錯(cuò)誤)。
現(xiàn)在歸納 清單 2 中的簡(jiǎn)單例子的基本方面:
CriteriaQuery
是一個(gè)查詢表達(dá)式節(jié)點(diǎn)樹。在傳統(tǒng)的基于字符串的查詢語言中,這些表達(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è)沒有父類的特殊 Path< T>
。
QueryBuilder
是 CriteriaQuery
和各種查詢表達(dá)式的工廠。
CriteriaQuery
被傳遞給一個(gè)可執(zhí)行查詢并保留類型信息,這樣可以直接訪問選擇列表的元素,而不需要任何運(yùn)行時(shí)強(qiáng)制類型轉(zhuǎn)換。
持久化域的元模型
討論 清單 2 時(shí)指出了一個(gè)不常見的構(gòu)造:Person_.age
,它表示 Person
的持久化屬性 age
。清單 2 使用 Person_.age
形成一個(gè)路徑表達(dá)式,它通過 p.get(Person_.age)
從 Root< Person>
表達(dá)式 p
導(dǎo)航而來。Person_.age
是 Person_
類中的公共靜態(tài)字段,Person_
是靜態(tài)、已實(shí)例化的規(guī)范元模型類,對(duì)應(yīng)于原來的 Person
實(shí)體類。
元模型類描述持久化類的元數(shù)據(jù)。如果一個(gè)類安裝 JPA 2.0 規(guī)范精確地描述持久化實(shí)體的元數(shù)據(jù),那么該元模型類就是規(guī)范的。規(guī)范的元模型類是靜態(tài)的,因此它的所有成員變量都被聲明為靜態(tài)的(也是 public
的)。Person_.age
是靜態(tài)成員變量之一。您可以在開發(fā)時(shí)在源代碼中生成一個(gè)具體的 Person_.java
來實(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"); |
不過,這種方法也存在很大的限制,類似于 清單 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 提供的解決辦法通過靜態(tà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; @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ū)⑦@個(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; } |
元模型類將原來的 domain.Person
實(shí)體的每個(gè)持久化屬性聲明為類型為 SingularAttribute< Person,?>
的靜態(tài)公共字段。通過利用這個(gè) Person_
元模型類,可以在編譯期間引用 domain.Person
的持久化屬性 age
— 不是通過 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器通過 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ù)表示該類聲明原來的持久化屬性和持久化屬性本身的類型。
元模型類被注釋為 @StaticMetamodel(domain.Person.class)
以將其標(biāo)記為一個(gè)與原來的持久化 domain.Person
實(shí)體對(duì)應(yīng)的元模型類。
Metamodel API
我將一個(gè)元模型類定義為一個(gè)持久化實(shí)體類的描述。就像 Reflection API 需要其他接口(比如 java.lang.reflect.Field
或 java.lang.reflect.Method
)來描述 java.lang.Class
的組成一樣,JPA Metamodel API 也需要其他接口(比如 SingularAttribute
和 PluralAttribute
)來描述元模型類的類型及其屬性。
圖 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ì)微的差別來表達(dá)關(guān)于持久化的豐富元信息。例如,Java Reflection API 將所有 Java 類型表示為 java.lang.Class
。即沒有通過獨(dú)立的定義對(duì)概念進(jìn)行區(qū)分,比如類、抽象類和接口。當(dāng)然,您可以詢問 Class
它是一個(gè)接口還是一個(gè)抽象類,但這與通過兩個(gè)獨(dú)立的定義表示接口和抽象類的差別不同。
Java Reflection API 在 Java 語言誕生時(shí)就被引入(對(duì)于一種常見的多用途編程語言而言,這曾經(jīng)是一個(gè)非常前沿的概念),但是經(jīng)過多年的發(fā)展才認(rèn)識(shí)到強(qiáng)類型系統(tǒng)的用途和強(qiáng)大之處。JPA Metamodel API 將強(qiáng)類型引入到持久化實(shí)體中。例如,持久化實(shí)體在語義上區(qū)分為 MappedSuperClass
、Entity
和 Embeddable
。在 JPA 2.0 之前,這種語義區(qū)分是通過持久化類定義中的對(duì)應(yīng)類級(jí)別注釋來表示的。JPA Metamodel 在 javax.persistence.metamodel
包中描述了 3 個(gè)獨(dú)立的接口( MappedSuperclassType
、EntityType
和 EmbeddableType
),以更加鮮明的對(duì)比它們的語義特征。類似地,可以通過接口(比如 SingularAttribute
、CollectionAttribute
和 MapAttribute
)在類型定義級(jí)別上區(qū)分持久化屬性。
除了方便描述之外,這些專門化的元模型接口還有實(shí)用優(yōu)勢(shì),能夠幫助構(gòu)建類型安全的查詢從而減少運(yùn)行時(shí)錯(cuò)誤。您在前面的例子中看到了一部分優(yōu)勢(shì),隨著我通過 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é)果將收到可怕的 ClassNotFoundException
或 NoClassDef FoundError
(對(duì)于處理包含多個(gè) ClassLoader
的環(huán)境的開發(fā)人員或部署人員而言,問題就復(fù)雜了)。
現(xiàn)在將一組嚴(yán)格的可相互引用的類稱為運(yùn)行時(shí)作用域,而在 JPA 1.0 中稱為持久化單元。持久化單元作用域的持久化實(shí)體在 META-INF/persistence.xml 文件的 < class>
子句中枚舉。在 JPA 2.0 中,通過 javax.persistence.metamodel.Metamodel
接口讓開發(fā)人員可以在運(yùn)行時(shí)使用作用域。Metamodel
接口是特定持久化單元知道的所有持久化實(shí)體的容器,如圖 5 所示:
圖 5. 元模型接口是持久化單元中的類型的容器
這個(gè)接口允許通過元模型元素的對(duì)應(yīng)持久化實(shí)體類訪問元模型元素。例如,要獲得對(duì) Person
持久化實(shí)體的持久化元數(shù)據(jù)的引用,可以編寫:
EntityManagerFactory emf = ...; Metamodel metamodel = emf.getMetamodel(); EntityType< Person> pClass = metamodel.entity(Person.class); |
這是一個(gè)用類的名稱通過 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)用程序可以通過 Metamodel API 在運(yùn)行時(shí)引用的屬性,是通過實(shí)例化靜態(tài)規(guī)范元模型 Person_
類向 Java 編譯器提供的。
除了將持久化實(shí)體分解為對(duì)應(yīng)的元模型元素之外,Metamodel API 還允許訪問所有已知的元模型類 (Metamodel.getManagedTypes()
),或者通過類的持久化信息訪問元模型類,例如 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ù)庫列。
規(guī)范和非規(guī)范
盡管 JPA 2.0 規(guī)范規(guī)定了規(guī)范的靜態(tài)元模型類的精確樣式(包括元模型類的完整限定名及其靜態(tài)字段的名稱),應(yīng)用程序也能夠編寫這些元模型類。如果應(yīng)用程序開發(fā)人員編寫元模型類,這些類就稱為非規(guī)范元模型?,F(xiàn)在,關(guān)于非規(guī)范元模型的規(guī)范還不是很詳細(xì),因此對(duì)非規(guī)范元模型的支持不能在 JPA 提供者之間移植。您可能已經(jīng)注意到,公共靜態(tài)字段僅在規(guī)范元模型中聲明,而沒有初始化。聲明之后就可以在開發(fā) CriteriaQuery
時(shí)引用這些字段。但是,必須在運(yùn)行時(shí)給它們賦值才有意義。盡管為規(guī)范元模型的字段賦值是 JPA 提供者的責(zé)任,但非規(guī)范元模型則不存在這一要求。使用非規(guī)范元模型的應(yīng)用程序必須依賴于特定供應(yīng)商機(jī)制,或開發(fā)自己的機(jī)制來在運(yùn)行時(shí)初始化元模型屬性的字段值。
注釋處理和元模型生成
如果您有許多持久化實(shí)體,您將傾向于不親自編寫元模型類,這是很自然的事情。持久化提供者應(yīng)該 為您生成這些元模型類。在規(guī)范中沒有強(qiáng)制規(guī)定這種工具或生成機(jī)制,但是 JPA 之間已經(jīng)私下達(dá)成共識(shí),他們將使用在 Java 6 編譯器中集成的 Annotation Processor 工具生成規(guī)范元模型。Apache OpenJPA 提供一個(gè)工具來生成這些元模型類,其生成方式有兩種,一是在您為持久化實(shí)體編譯源代碼時(shí)隱式地生成,二是通過顯式地調(diào)用腳本生成。在 Java 6 以前,有一個(gè)被廣泛使用的稱為 apt
的 Annotation Processor 工具,但在 Java 6 中,編譯器和 Annotation Processor 的合并被定義為標(biāo)準(zhǔn)的一部分。
要像持久化提供者一樣在 OpenJPA 中生成這些元模型類,僅需在編譯器的類路徑中使用 OpenJPA 類庫編譯 POJO 實(shí)體:
$ javac domain/Person.java |
將生成規(guī)范元模型 Person_
類,它將位于 Person.java 所在的目錄,并且作為該編譯的一部分。
編寫類型安全的查詢
到目前為止,我已經(jīng)構(gòu)建了 CriteriaQuery
的組件和相關(guān)的元模型類?,F(xiàn)在,我將展示如何使用 Criteria API 開發(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()
子句。
該查詢表達(dá)式是一個(gè)構(gòu)建塊,可以通過組裝它為查詢定義最后的選擇謂詞。清單 6 中的例子顯示了通過導(dǎo)航到 Account
的余額創(chuàng)建的 Path
表達(dá)式,然后 Path
表達(dá)式被用作兩個(gè)二進(jìn)制函數(shù)表達(dá)式( greaterThan()
和 lessThan()
)的輸入表達(dá)式,這兩個(gè)表達(dá)式的結(jié)果都是一個(gè)布爾表達(dá)式或一個(gè)謂詞。然后,通過 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è)例子通過兩個(gè)步驟從 Account
進(jìn)行導(dǎo)航,創(chuàng)建一個(gè)表示帳戶所有者的名稱的路徑。然后,它創(chuàng)建一個(gè)使用路徑表達(dá)式作為輸入的 in()
表達(dá)式。in()
表達(dá)式計(jì)算它的輸入表達(dá)式是否等于它的參數(shù)之一。這些參數(shù)通過 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í)體連接 起來。CriteriaQuery
通過類型連接表達(dá)式 連接兩個(gè)實(shí)體。類型連接表達(dá)式有兩個(gè)類型參數(shù):連接源的類型和連接目標(biāo)屬性的可綁定類型。例如,如果您想查詢有一個(gè)或多個(gè) PurchaseOrder
沒有發(fā)出的 Customer
,則需要通過一個(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
、ListJoin
和 MapJoin
。(圖 1 顯示了各種連接表達(dá)式)。在 清單 8 的第 3 行不需要進(jìn)行顯式的轉(zhuǎn)換,因?yàn)?CriteriaQuery
和 Metamodel API 通過覆蓋 join()
的方法能夠識(shí)別和區(qū)分聲明為 java.util.Collection
或 List
或者 Set
或 Map
的屬性類型。
在查詢中使用連接在連接實(shí)體上形成一個(gè)謂詞。因此,如果您想要選擇有一個(gè)或多個(gè)未發(fā)送 PurchaseOrder
的 Customer
,可以通過狀態(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è)來自相同表達(dá)式 c
的連接表達(dá)式的等同性斷言將失敗。因此,如果查詢的謂詞涉及到未發(fā)送并且值大于 $200 的 PurchaseOrder
,那么正確的構(gòu)造是將 PurchaseOrder
與根 Customer
表達(dá)式連接起來(僅一次),把生成的連接表達(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
)或位置方式(前面帶有問號(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ù)值綁定到查詢。不過,您不可以使用位置參數(shù)。線性 JPQL 查詢字符串中的整數(shù)位置有一定的意義,但是不能在概念模型為查詢表達(dá)式樹的 CriteriaQuery
上下文中使用整數(shù)位置。
JPA 查詢參數(shù)的另一個(gè)有趣方面是它們沒有內(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è)條件。可以通過兩種方式之一在 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 中,來自 Account
的查詢沒有顯式地指定它的選擇條件,并且和顯式地選擇的候選類一樣。清單 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í)體本身,那么可以通過其他幾個(gè)構(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)建了一個(gè)由其他幾個(gè)可選擇的表達(dá)式組成的預(yù)測(cè)條件。construct()
方法創(chuàng)建給定類參數(shù)的一個(gè)實(shí)例,并使用來自輸入選擇條件的值調(diào)用一個(gè)構(gòu)造函數(shù)。例如,如果 CustomerDetails
— 一個(gè)非持久化實(shí)體 — 有一個(gè)接受 String
和 int
參數(shù)的構(gòu)造方法,那么 CriteriaQuery
可以通過從選擇的 Customer
— 一個(gè)持久化實(shí)體 — 實(shí)例的名稱和年齡創(chuàng)建實(shí)例,從而返回 CustomerDetails
作為它的結(jié)果,如清單 14 所示:
清單 14. 通過 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ù)索引訪問(類似于熟悉的 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); |
這個(gè)查詢返回一個(gè)結(jié)果列表,它的每個(gè)元素都是一個(gè) Tuple
。反過來,每個(gè)二元組都帶有兩個(gè)元素 — 可以被每個(gè) TupleElement
的索引或別名(如果有的話)訪問,或直接被 TupleElement
訪問。清單 16 中需要注意的兩點(diǎn)是 alias()
的使用,它是將一個(gè)名稱綁定到查詢表達(dá)式的一種方式(創(chuàng)建一個(gè)新的副本),和 QueryBuilder
上的 createTupleQuery()
方法,它僅是 createQuery(Tuple.class)
的代替物。
這些能夠改變結(jié)果的方法的行為和在構(gòu)造期間被指定為 CriteriaQuery
的類型參數(shù)結(jié)果共同組成 multiselect()
方法的語義。這個(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é)果類型或沒有指定類型參數(shù)時(shí),multiselect()
的行為會(huì)變得更加有趣。在這些情況中,如果 multiselect()
使用單個(gè)輸入條件,那么返回值將為所選擇的條件。但是如果 multiselect()
包含多個(gè)輸入條件,結(jié)果將得到一個(gè) Object[]
。
高級(jí)特性
到目前為止,我主要強(qiáng)調(diào)了 Criteria API 的強(qiáng)類型,以及它如何幫助減少出現(xiàn)在基于字符串 JPQL 查詢中的語義錯(cuò)誤。Criteria API 還是以編程的方式構(gòu)建查詢的機(jī)制,因此通常被稱為動(dòng)態(tài) 查詢 API。編程式查詢構(gòu)造 API 的威力是無窮的,但它的利用還取決于用戶的創(chuàng)造能力。我將展示 4 個(gè)例子:
使用弱類型的 API 構(gòu)建動(dòng)態(tài)查詢
使用數(shù)據(jù)庫支持的函數(shù)作為查詢表達(dá)式來擴(kuò)展語法
編輯查詢實(shí)現(xiàn) “在結(jié)果中搜索” 功能
根據(jù)例子進(jìn)行查詢 — 數(shù)據(jù)庫社區(qū)熟悉的模式
弱類型和動(dòng)態(tài)查詢構(gòu)建
Criteria API 的強(qiáng)類型檢查基于開放期間的實(shí)例化元模型類的可用性。不過,在某些情況下,選擇的實(shí)體僅能夠在運(yùn)行時(shí)決定。為了支持這種用法,Criteria API 方法提供一個(gè)并列版本,其中持久化屬性通過它們的名稱進(jìn)行引用(類似于 Java Reflection API),而不是引用實(shí)例化靜態(tài)元模型屬性。該 API 的這個(gè)并列版本可以通過犧牲編譯時(shí)類型檢查來真正地支持動(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))); |
不過,弱類型 API 不能夠返回正確的泛型表達(dá)式,因此生成一個(gè)編輯器來警告未檢查的轉(zhuǎn)換。一種消除這些煩人的警告消息的方法是使用 Java 泛型不常用的工具:參數(shù)化方法調(diào)用,比如 清單 19 中通過調(diào)用 get()
方法獲取路徑表達(dá)式。
可擴(kuò)展數(shù)據(jù)庫表達(dá)式
動(dòng)態(tài)查詢構(gòu)造機(jī)制的獨(dú)特優(yōu)勢(shì)是它的語法是可擴(kuò)展的。例如,您可以在 QueryBuilder
接口中使用 function()
方法創(chuàng)建數(shù)據(jù)庫支持的表達(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ù)庫的查詢。例如,MySQL 數(shù)據(jù)庫支持 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ù)庫的函數(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法僅支持固定數(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 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(); |