Hibernate ORM 用戶手冊 第16章 Criteria

Hibernate ORM 用戶手冊 第16章 Criteria

Criteria查詢提供了一種類型安全的針對HQL、JPQL和原生SQL查詢的替代方案。

Hibernate曾經提供了一種舊的、傳統的org.hibernate.Criteria API,現在應該當做過期功能對待。它們將不再收到任何新功能開發。最終,Hibernate特有的Criteria查詢功能會移植做爲JPAjavax.persistence.criteria.CriteriaQuery的擴展。要了解org.hibernate.Criteria API的相關內容,請參考《傳統Hibernate Criteria查詢》

本章將集中討論基於JPA API來聲明類型安全的Criteria查詢。

Criteria查詢是一種編程式、類型安全的用於表達查詢的手段。因爲它使用接口和類表示查詢的各種結構部分(例如查詢本身,select子句或order-by等),所以是類型安全的。此外,屬性的引用也可以是類型安全的,我們稍後會看到。使用過舊的Hibernate org.hibernate.Criteria API的用戶會發現其實JPA API和前者的目的是一致的,但是我們相信JPA API更加優越。這種信念是因爲JPA API清晰地流露出了它從其他API中學到的經驗。

Criteria查詢本質上是一個類圖,類圖的每部分都代表了這個查詢中的一部分原子性增量(在向下瀏覽類圖的時候)。要想開啓一個Criteria查詢,第一步是就是構建此類圖。使用Criteria查詢時首先要熟悉接口javax.persistence.criteria.CriteriaBuilder。這是一個用於構建Criteria查詢中每個獨立部件的工廠。我們可以通過javax.persistence.EntityManagerFactoryjavax.persistence.EntityManager中的getCriteriaBuilding()方法來獲取一個javax.persistence.criteria.CriteriaBuilder實例。

下一步則是獲取javax.persistence.criteria.CriteriaQuery實例。可以通過javax.persistence.criteria.CriteriaBuilding中的如下方法達到這一目的:

  • <T> CriteriaQuery<T> createQuery(Class<T> resultClass)
  • CriteriaQuery<Tuple> createTupleQuery()
  • CriteriaQuery<Object> createQuery()

取決於查詢結果的不同預期類型,每個方法的行爲都不相同。

《JPA標準說明書》第6章 Criteria API 中已經包含了爲數不少的關於Criteria查詢各方面的參考材料。與其重複這些內容,倒不如來看一些更滿足期待的使用場景。

16.1 擁有類型的Criteria查詢

Criteria查詢的類型(又稱<T>)描述的是查詢結果的期望類型。它可以是一個實體、一個Integer或其他任意對象。

16.2 選擇實體

這可能是查詢最常見形式。在應用中選擇實體的實例。

示例 533:選擇根實例

CriteriaBuilder builder = entityManager.getCriteriaBuilder();

CriteriaQuery<Person> criteria = builder.createQuery(Person.class);
Root<Person> root = criteria.from(Person.class);
criteria.select(root);
criteria.where(builder.equal(root.get(Person_.name), "John Doe"));

List<Person> persons = entityManager.createQuery(criteria).getResultList();

示例代碼通過給createQuery()方法傳遞類引用,指定查詢結果對象應該屬於Person類。

在示例代碼中,CriteriaQuery#select的調用是非必要的,因爲我們只有一個查詢根root,所以缺省就會選擇root

Person_.name是靜態形式的JPA元模型引用的一個實例。本章我們將只使用這種形式。參考文檔《Hibernate JPA 元模型生成器》瞭解更多關於JPA靜態元模型的信息。

16.3 選擇表達式

選擇表達式的最簡形式是選擇一個實體特定的某個屬性。但也可用於選擇聚合體、算術操作等。

示例534:選擇一個屬性

CriteriaBuilder builder = entityManager.getCriteriaBuilder();

CriteriaQuery<String> criteria = builder.createQuery(String.class);
Root<Person> root = criteria.from(Person.class);
criteria.select(root.get(Person_.nickName));
criteria.where(builder.equal(root.get(Person_.name), "John Doe"));

List<String> nickNames = entityManager.createQuery(criteria).getResultList();

在這個示例中,查詢結果的期望類型是java.lang.StringPerson#nickName屬性的類型是java.lang.String)。由於查詢結果可以包含對Person實體中多個屬性的引用,所以總是需要我們指定要查詢的屬性。這是通過調用Root#get方法來實現的。

16.4 選擇多個值

事實上通過Criteria查詢多個值有多種實現方式,本文會介紹其中兩種。此外作爲近似替代方案,推薦使用在 《16.6 元組Criteria查詢》 中介紹的元組。或者,考慮試試在 《16.5 選擇包裝對象》 介紹的包裝器查詢。

示例535:選擇一個數組

CriteriaBuilder builder = entityManager.getCriteriaBuilder();

CriteriaQuery<Object[]> criteria = builder.createQuery(Object[].class);
Root<Person> root = criteria.from(Person.class);

Path<Long> idPath = root.get(Person_.id);
Path<String> nickNamePath = root.get(Person_.nickName);

criteria.select(builder.array(idPath, nickNamePath));
criteria.where(builder.equal(root.get(Person_.name), "John Doe"));

List<Object[]> idAndNickNames = entityManager.createQuery(critria).getResultList();

技術上講,這被歸類爲類型查詢,但從處理結果上可以看到有一些誤導。不論如何,這裏查詢結果的期望類型是一個數組。

示例代碼通過javax.persistence.criteria.CriteriaBuilder裏的array方法將獨立選擇顯式組合爲javax.persistence.criteria.CompoundSelection對象。

示例536:通過multiselect選擇數組

CriteriaBuilder builder = entityManager.getCriteriaBuilder();

CriteriaQuery<Object[]> criteria = builder.createQuery(Object[].class);
Root<Person> root = criteria.from(Person.class);

Path<Long> idPath = root.get(Person_.id);
Path<String> nickNamePath = root.get(Person_.nickName);

criteria.multiselect(idPath, nickNamePath);
criteria.where(builder.equal(root.get(Person._name), "John Doe"));

List<Object[]> idAndNickNames = entityManager.createQuery(criteria).getResultList();

示例535一樣,我們創建了一個返回Object數組的Criteria類型查詢。這兩個查詢在功能上是等價的。後者使用了multiselect()方法,其行爲根據構建CriteriaQuery時傳入的Class不同而略微不同。在本示例中,它表示選擇並返回一個Object[]

16.5 選擇包裝對象

選擇一個“包裝”了多個值的對象,是一種選擇多個值的替代方案。回到上文的示例,與其返回一個[Person#id, Person#nickName]類型的數組,倒不如聲明一個可以持有這些值的類,並返回該類的對象。

示例537:選擇一個包裝對象

public class PersonWrapper {
    private final Long id;
    private final String nickName;

    public PersonWrapper(Long id, String nickName) {
        this.id = id;
        this.nickName = nickName;
    }

    public Long getId() {
        return id;
    }

    public String getNickName() {
        return nickName;
    }
}

CriteriaBuilder builder = entityManager.getCriteriaBuilder();

CriteriaQuery<PersonWrapper> criteria = builder.createQuery(PersonWrapper.class);

Path<Long> idPath = root.get(Person_.id);
Path<String> nickNamePath = root.get(Person_.nickName);

criteria.select(builder.construct(PersonWrapper.class, idPath, nickNamePath));
criteria.where(builder.equal(root.get(Person_.name)), "John Doe");

List<PersonWrapper> wrappers = entityManager.createQuery(criteria).getResultList();

示例首先定義了一個簡單的包裝類,它會用來包裝結果值。要特別注意一下它的構造器和構造器的參數類型。因爲使用了PersonWrapper作爲Criteria查詢的類型,所以之後我們會收到PersonWrapper類型的對象。

這個實例教給我們如何使用javax.persistence.criteria.CriteriaBuilder中的construct方法來創建包裝表達式。它會通過對應參數類型的構造函數,將每一行查詢結果作爲參數傳給構造函數以實例化PersonWrapper對象。之後它便會作爲選擇表達式傳入Criteria查詢。

16.6 元組(Tuple)Criteria查詢

更好的查詢多個值的方法,除了包裝對象(上文剛剛討論過),還有javax.persistence.Tuple協議。

示例538:選擇一個元組

CriteriaBuilder builder = entityManager.getCriteriaBuilder();

CriteriaQuery<Tuple> criteria = builder.createQuery(Tuple.class);
Root<Person> root = criteria.from(Person.class);

Path<Long> idPath = root.get(Person_.id);
Path<String> nickNamePath = root.get(Person_.nickName);

criteria.multselect(idPath, nickNamePath);
criteria.where(builder.equal(root.get(Person_.name), "John Doe"));

List<Tuple> tuples = entityManager.createQuery(criteria).getResultList();

for (Tuple tuple: tuples) {
    Long id = tuple.get(idPath);
    String nickName = tuple.get(nickNamePath);
}
// 也可以使用索引
for (Tuple tuple: tuples) {
    Long id = (Long) tuple.get(0);
    String nickName = (String) tuple.get(1);
}

這個示例展示瞭如何通過javax.persistence.Tuple接口訪問查詢結果。在這裏我們使用的是javax.persistence.criteria.CriteriaBuilder下的createQuery(Tuple.class)方法,此外也可以顯式地調用createTupleQuery()

我們又見到multiselect()了,上次是在示例536中。不同之處是這次查詢的類型是javax.persistence.Tuple,故這次複合查詢的結果會解析成元組對象。

javax.persistence.Tuple協議提供了元組元素的三種訪問方式:

  • 類型式
    示例538展示了這種方式,通過調用tuple.get(idPath)tuple.get(nickNamePath)來訪問元素。本方式基於構建查詢時使用的javax.persistence.TupleElement表達式對元組裏的值進行類型化訪問。
  • 位置式
    基於位置訪問元組內的值。簡單形式Object get(int position)示例535示例536中的寫法很像。<X> X get(int position, Class<X> type)可以實現基於位置的類型化訪問,但前提是參數聲明的類型必須契合元組值的類型。
  • 別名式
    通過(可選的)別名聲明訪問元組下的值。示例代碼中的查詢並沒有指定別名。別名的指定是通過javax.persistence.criteria.Selection下的alias方法實現的。和位置式訪問類似,別名式訪問也包含無類型的Object get(String alias)和類型化的<X> X get(String alias, Class<X> type)兩種訪問方式。

16.7 FROM子句

CriteriaQuery對象定義了基於一個或多個實體、嵌入體或基本抽象元類型的查詢。查詢的根對象,是可以訪問到其他類型對象的一個或多個實體對象。

—— JPA 標準說明書(P262), 6.5.2 查詢的根

FROM子句中所有的獨立部分(根、連接、路徑)均實現了javax.persistence.criteria.From接口。

16.8 根(Root)

根定義了在查詢中所有可用的連接、路徑和屬性的基本信息。根總是實體類型的。Criteria查詢中根的定義和添加是通過javax.persistence.criteria.CriteriaQuery裏的重載方法實現的。

示例539:根的方法

<X> Root<X> from(Class<X> type);
<X> Root<X> from (EntityType<X> entityType);

示例540:添加一個根

CriteriaBuilder builder = entityManager.getCriteriaBuilder();

CriteriaQuery<Person> criteria = builder.createQuery(Person.class);
Root<Person> root = criteria.from(Person.class);

Criteria查詢可能會定義多個根,其結果是會創建新增根與其他根之間的笛卡爾積。下文是定義PersonPartner實體間笛卡爾積的示例:

示例541:添加多個根

CriteriaBuilder builder = entityManager.getCriteriaBuilder();

CriteriaQuery<Tuple> criteria = builder.createQuery(Tuple.class);

Root<Person> personRoot = criteria.from(Person.class);
Root<Partner> partnerRoot = criteria.from(partner.class);
criteria.multiselect(personRoot, partnerRoot);

Predicate personRestriction = builder.and(
    builder.equal(personRoot.get(Person_.address), address),
    builder.isNotEmpty(personRoot.get(Person_phones))
);
Predicate partnerRestriction = builder.and(
    builder.like(partnerRoot.get(Partner_.name), prefix),
    builder.equal(partnerRoot.get(Partner_.version), 0)
);
criteria.where(builder.and(personRestriction, partnerRestriction));

List<Tuple> tuples = entityManager.createQuery(criteria).getResultList();

16.9 連接(Join)

連接可以實現對其他javax.persistence.criteria.From聯合屬性或嵌入式屬性的訪問。連接可以通過javax.persistence.criteria.From接口中一系列join方法的重載創建。

示例542:連接示例

CriteriaBuilder builder = entityManager.getCriteriaBulder();

CriteriaQuery<Phone> criteria = builder.createQuery(Phone.class);
Root<Phone> root = criteria.from(Phone.class);

// Phone.person是一個@ManyToOne(多對一)
Join<Phone, Person> personJoin = root.join(Phone._person);
// Person.addresses是一個@ElementCollection(元素集合)
Join<Person, String> addressesJoin = personJoin.join(Person._addresses);

criteria.where(builder.isNotEmpty(root.get(Phone_.calls)));

List<Phone> phones = entityManager.createQuery(criteria).getResultList();

16.10 拉取(Fetch)

和HQL、JPQL類似,Criteria查詢也可以指定拉取擁有者的關聯數據。拉取可以通過javax.persistence.criteria.From接口中的一系列fetch重載方法創建。

示例543:連接拉取示例

CriteriaBuilder builder = entityManager.getCriteriaBuilder();

CriteriaQuery<Phone> criteria = builder.createQuery(Phone.class);
Root<Phone> root = criteria.from(Phone.class);

// Phone.person是一個@ManyToOne
Fetch<Phone, Person> personFetch = root.fetch(Phone_.person);
// Person.addresses是一個@ElementCollection
Fetch<Person, String> addressesJoin = personFetch.fetch(Person_.addresses);

criteria.where(builder.isNotEmpty(root.get(Phone_.calls)));

List<Phone> phones = entityManager.createQuery(criteria).getResultList();

從技術上講,嵌入式屬性總會會跟隨其擁有者被拉取。然而,因爲默認的集合元素是LAZY的,所以我們需要通過javax.persistence.criteria.Fetch對象來拉取Phone#addresses

16.11 路徑表達式

根、連接和拉取本身都是路徑。

16.12 使用參數

示例544:參數示例

CriteriaBuilder builder = entityManager.getCriteriaBuilder();

CriteriaQuery<Person> criteria = builder.createQuery(Person.class);
Root<Person> root = criteria.from(Person.class);

ParameterExpression<String> nickNameParameter = builder.parameter(String.class);
criteria.where(builder.equal(root.get(Person_.nickName), nickNameParameter));

TypedQuery<Person> query = entityManager.createQuery(criteria);
query.setParameter(nickNameParameter, "JD");
List<Person> persons = query.getResultList();

使用javax.persistence.criteria.CriteriaBuilder中的parameter方法可以獲取參數對象的引用。之後可以通過這個引用給javax.persistence.Query綁定參數值。

16.13 group by 的使用

注:這裏原文沒有使用之前一直使用的基於JPA靜態元模型獲取root的模式,推測應該是文檔這一小節一直沒有更新。

示例545:分組查詢示例

CriteriaBuilder builder = entityManager.getCriteriaBuilder();

CriteriaQuery<Tuple> criteria = builder.createQuery(Tuple.class);
Root<Person> root = criteria.from(Person.class);

criteria.groupBy(root.get("address"));
criteria.multiselect(root.get("address"), builder.count(root));

List<Tuple> tuples = entityManager.createQuery(criteria).getResultList();

for (Tuple tuple : tuples) {
    String name = (String) tuple.get(0);
    Long count = (Long) tuple.get(1);
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章