【译】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);
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容