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

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

編輯:J2EE

持久化域的元模型

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

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

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

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

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

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

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


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

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 提供者能夠將這個類的實例作為持久化實體管理。

清單 4 顯示了 domain.Person 的對應靜態規范元模型類:


清單 4. 一個簡單實體的規范元模型

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,?> 的靜態公共字段。通過利用這個 Person_ 元模型類,可以在編譯期間引用 domain.Person 的持久化屬性 age — 不是通過 Reflection API,而是直接引用靜態的 Person_.age 字段。然後,編譯器可以根據 age 屬性聲明的類型實施類型檢查。我已經列舉了一個關於此類限制的例子:QueryBuilder.gt(p.get(Person_.age), "xyz") 將導致編譯器錯誤,因為編譯器通過 QueryBuilder.gt(..) 的簽名和 Person_.age 的類型可以確定 Personage 屬性是一個數字字段,不能與 String 進行比較。

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

  • 元模型 Person_.age 字段被聲明為類型 Javax.persistence.metamodel.SingularAttributeSingularAttribute 是 JPA Metamodel API 中定義的接口之一,我將在下一小節描述它。SingularAttribute< Person, Integer> 的泛型參數表示該類聲明原來的持久化屬性和持久化屬性本身的類型。
  • 元模型類被注釋為 @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 中的持久化類型的接口的層次結構
圖 3. Metamodel API 中的持久化類型的接口的層次結構

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


圖 4. Metamodel API 中的持久化屬性的接口的層次結構
圖 4. Metamodel API 中的持久化屬性的接口的層次結構

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

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

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

運行時作用域

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

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


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

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

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))上調用一個方法,它將返回一個 SingularAttribute< Person, Integer> 實例,該實例與實例化規范元模型類的靜態 Person_.age 成員相同。最重要的是,對於應用程序可以通過 Metamodel API 在運行時引用的屬性,是通過實例化靜態規范元模型 Person_ 類向 Java 編譯器提供的。

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

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

規范和非規范

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

注釋處理和元模型生成

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

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

$ javac domain/Person.Java

將生成規范元模型 Person_ 類,它將位於 Person.Java 所在的目錄,並且作為該編譯的一部分。

編寫類型安全的查詢

到目前為止,我已經構建了 CriteriaQuery 的組件和相關的元模型類。現在,我將展示如何使用 Criteria API 開發一些查詢。

函數表達式

函數表達式將一個函數應用到一個或多個輸入參數以創建新的表達式。函數表達式的類型取決於函數的性質及其參數的類型。輸入參數本身可以是表達式或文本值。編譯器的類型檢查規則與 API 簽名結合確定什麼是合法輸入。

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


清單 5. CriteriaQuery 中的函數表達式

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

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

等效的 JPQL 查詢為:

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

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

該查詢表達式是一個構建塊,可以通過組裝它為查詢定義最後的選擇謂詞。清單 6 中的例子顯示了通過導航到 Account 的余額創建的 Path 表達式,然後 Path 表達式被用作兩個二進制函數表達式( greaterThan()lessThan())的輸入表達式,這兩個表達式的結果都是一個布爾表達式或一個謂詞。然後,通過 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 查詢為:

"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 進行導航,創建一個表示帳戶所有者的名稱的路徑。然後,它創建一個使用路徑表達式作為輸入的 in() 表達式。in() 表達式計算它的輸入表達式是否等於它的參數之一。這些參數通過 value() 方法在 In< T> 表達式上指定,In< T> 的簽名如下所示:

In< T> value(T value); 

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

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

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

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

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

連接關系

盡管 清單 6清單 7 中的例子將表達式用作構建塊,查詢都是基於一個實體及其屬性之上的。但是查詢通常涉及到多個實體,這就要求您將多個實體連接 起來。CriteriaQuery 通過類型連接表達式 連接兩個實體。類型連接表達式有兩個類型參數:連接源的類型和連接目標屬性的可綁定類型。例如,如果您想查詢有一個或多個 PurchaSEOrder 沒有發出的 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 創建,持久化屬性 Customer.orders 由連接源(Customer)和 Customer.orders 屬性的可綁定類型進行參數化,可綁定類型是 PurchaSEOrder不是 已聲明的類型 Java.util.Set< PurchaSEOrder>。此外還要注意,因為初始屬性的類型為 Java.util.Set,所以生成的連接表達式為 SetJoin,它是專門針對類型被聲明為 Java.util.Set 的屬性的 Join。類似地,對於其他受支持的多值持久化屬性類型,該 API 定義 CollectionJoinListJoinMapJoin。(圖 1 顯示了各種連接表達式)。在 清單 8 的第 3 行不需要進行顯式的轉換,因為 CriteriaQuery 和 Metamodel API 通過覆蓋 join() 的方法能夠識別和區分聲明為 Java.util.CollectionList 或者 SetMap 的屬性類型。

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

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

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


清單 9. 每次連接創建一個唯一的實例

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

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

使用參數

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

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

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

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

參數化查詢在查詢執行之前綁定參數的值:

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

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


清單 10. 在 CriteriaQuery 中使用參數

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

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

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

JPA 查詢參數的另一個有趣方面是它們沒有內部值。值綁定到可執行查詢上下文中的參數。因此,可以合法地從相同的 CriteriaQuery 創建兩個獨立可執行的查詢,並為這些可執行查詢的相同參數綁定兩個整數值。

預測結果

您已經看到 CriteriaQuery 在執行時返回的結果已經在 QueryBuilder 構造 CriteriaQuery 時指定。查詢的結果被指定為一個或多個預測條件。可以通過兩種方式之一在 CriteriaQuery 接口上指定預測條件:

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

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


清單 11. CriteriaQuery 默認選擇的候選區段

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

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


清單 13. 生成查詢結果的方法

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

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


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

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 顯示了如何將結果包裝到 Object[] 中:


清單 15. 將結果包裝到 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();

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

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


清單 16. 將查詢結果包裝到 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);

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

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


清單 17. 基於結果類型的 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));

因為查詢結果類型為 CustomerDetailsmultiselect() 將其預測條件解釋為 CustomerDetails 構造方法參數。如將查詢指定為返回 Tuple,那麼帶有相同參數的 multiselect() 方法將創建 Tuple 實例,如清單 18 所示:


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

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

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

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved