Hibernate ORM 用户手册 第16章 Criteria

3 篇文章 0 订阅
1 篇文章 0 订阅

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);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值