美文网首页
【译】Hibernate ORM 用户手册 第16章 Crite

【译】Hibernate ORM 用户手册 第16章 Crite

作者: hlwz5735 | 来源:发表于2019-06-14 14:23 被阅读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);
    }
    

    相关文章

      网友评论

          本文标题:【译】Hibernate ORM 用户手册 第16章 Crite

          本文链接:https://www.haomeiwen.com/subject/ppzufctx.html