分类
persistence

JPA 条件查询

JPA Criteria Queries

1. 概述

本文将围绕Spring Data JPA以及Hibernate中的Criteria Queries(条件查询)进行讨论,该特性是JPA的一个非常重要的特性,广泛的应用于各种逻辑查询中。在使用Criteria Queries的过程中,可以完全使用面向对象的思想,整个过程中不需要写任何的sql语句,这也正是Hibernate的主要特性。使用Criteria API可以使用编码的方式创建动态的查询语句,这使得我们可以根据实现的情况与需求,结合具体的业务来创建条件查询。

自Hibernate 5.2以后,Hibernate Criteria API被官方声明为弃用方法,转而推荐使用JPA Criteria API。本文将围绕如何使用Hibernate以及 JPA创建条件查询来展开讨论。

2. Maven依赖

在单独使用Hibernate的情况下,需要加入Hibernate依赖:

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-core</artifactId>   
    <version>5.3.2.Final</version>
</dependency>

最新的Hibernate版本可以在Maven官方仓库中获取。

如果你使用是Spring Boot,则应该加入Sprinb Data JPA依赖:

<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

注意:我们提供的代码示例基于Spring Boot,采用了Hibernate原生方法。

3. 综合查询示例

在开始展示如何使用条件查询Criteria queries之前,让我们做一些准备工作。首先,我们创建一个学生实体:

/**
 * 学生
 */
@Entity
public class Student {
    @Id
    @GeneratedValue
    private Long id;

    /**
     * 姓名
     */
    private String name;

    /**
     * 学号
     */
    private String no;

    /**
     * 体重
     */
    private Integer weight;

    // 以下省略构造函数及setter/getter,详情下载参阅我们提供的代码示例。

}

接下来,让我们看下如果使用条件查询来获取数据中的所有学生student信息:

    /**
     * 查找所有
     *
     * @return
     */
    @Transactional
    public List<Student> findAll() {
        Session session = this.getSession(); // ❶
        CriteriaBuilder cb = session.getCriteriaBuilder(); // ❷
        CriteriaQuery<Student> cq = cb.createQuery(Student.class); // ❸

        Root<Student> root = cq.from(Student.class); // ❹
        cq.select(root); // ❹

        return session.createQuery(cq).getResultList(); // ❺
    }

上述代码中我们主要完成了如下功能:

  1. 获取当前的Session实例。
  2. 调用Session中的getCriteriaBuilder()来获取一个CriteriaBuilder
  3. 调用CriteriaBuilder中的createQuery来获取一个CriteriaQuery
  4. 指定在查询结果中返回的数据项
  5. 根据CriteriaQuery创建查询并获取查询结果。

3.1 表达式Expressions

CriteriaBuilder可以创建特定的查询条件,我们把这些查询条件称为表达式ExpressionsCriteriaQuerywhere()方法可以接收表达式Expressions,并做为最终的查询的条件。

下面,我们给出几种表达式Expressions的示例:

查找体重大于X的学生:

cq.select(root).where(cb.gt(root.get("weight"), weight))

查找体重小于X的学生:

                cq.select(root).where(cb.lt(root.get("weight"), weight))

姓名包含X的学生:

                cq.select(root).where(cb.like(root.get("name"), "%" + name + "%"))

体重介于X(不包含)与Y(不包含)之间的学生:

                cq.select(root).where(cb.between(root.get("weight"), minWeight, maxWeight))

学号为null的学生:

                cq.select(root).where(cb.isNull(root.get("no")))

学号不为null的学生:

                cq.select(root).where(cb.isNotNull(root.get("no")))

除此以外,我们还只可以使用isEmpty()以及isNotEmpty()查询出某些字为空或不为空的数据。

刚刚我们展示几种查询的方式,但有个共同的特点:仅限于1个查询条件,那么当需要有多个查询条件时,比如我们希望查询出学生姓名以X打头并且学号不为null的所有学生,那么应该怎么做呢?

条件数组查询:

Predicate[] predicates = new Predicate[2];
predicates[0] = cb.isNotNull(root.get("no"));
predicates[1] = cb.like(root.get("name"), name + "%");
cq.select(root).where(predicates);

将Predicate数组做为表达式传入将查询出符合数组中所有条修的的数据,如果我们想进行OR查询,则可以这么做:

Predicate weightGt = cb.gt(root.get("weight"), weight);
Predicate nameLike = cb.like(root.get("name"), name + "%");
cq.select(root).where(cb.or(weightGt, nameLike));

有了OR查询,当然也可以使用AND查询:

Predicate weightGt = cb.gt(root.get("weight"), weight);
Predicate nameLike = cb.like(root.get("name"), name + "%");
cq.select(root).where(cb.and(weightGt, nameLike));

3.2 排序

Criteria除支持条件查询外,还支持按特定的字段排序:

按体重逆序排列+按姓名升序排列:

cq.select(root).orderBy(
        cb.desc(root.get("weight")),
        cb.asc(root.get("name"))

3.3 方法

Criteria还对方法提供了良好的支持:

获取全部条数的count查询:

cq.select(cb.count(root))

获取全部学生的平均身高的average:

cq.select(cb.avg(root.get("weight")))

Criteria除支持如上count、avg函数以外,还sum()max()min() 等其它函数提供了良好的支持。

3.4 更新数据CriteriaUpdate

自JPA 2.1开始,Criteria API提供了更新功能,该功能主要是通过CriteriaUpdate中的set()方法实现的:

public void updateNoByName(String no, String name) {
    Session session = this.getSession();
    CriteriaBuilder cb = session.getCriteriaBuilder();
    CriteriaUpdate<Student> criteriaUpdate = cb.createCriteriaUpdate(Student.class);
    Root<Student> root = criteriaUpdate.from(Student.class);

    criteriaUpdate.set("no", no);
    criteriaUpdate.where(cb.equal(root.get("name"), name));

    session.createQuery(criteriaUpdate).executeUpdate();
}

上述代码中,我们通过CriteriaBuilder创建了一个CriteriaUpdate<Student>实例,然后调用其set()方法更新了学号no字段,并最终调用了executeUpdate()完成了数据更新工作。

3.5 删除数据CriteriaDelete

CriteriaDelete提供了数据删除的功能。通过CriteriaDelete的where()方法来指定删除的条件,并调用executeUpdate()来完成删除工作:

public void deleteById(Long id) {
    Session session = this.getSession();
    CriteriaBuilder cb = session.getCriteriaBuilder();
    CriteriaDelete<Student> criteriaDelete = cb.createCriteriaDelete(Student.class);
    Root<Student> root = criteriaDelete.from(Student.class);

    criteriaDelete.where(cb.equal(root.get("id"), id));

    session.createQuery(criteriaDelete).executeUpdate();
}

4. 与HQL比较

在前面几个小节中我们阐述了如何使用条件查询Criteria Queries。

很明显,相对于HQL我们使用Criteria Queries创建了更加优雅、漂亮、干净的代码。

由于在使用Criteria Queries的过程中可以加入任何逻辑判断,所以我们可以依托其满足更加灵活的查询条件。相较于HQL,Criteria Queries更加的面向对象、较少的涉及了数据表中的字段名称,当然的也更不容易出现拼写错误。

当然了,凡事都有两面性。Criteria Queries也并不完美,特别是写一些关联查询的时候,使用Criteria Queries将会显得更加臃肿。

所以编程这事没有最好,只有最合适。无论什么技术,最终都是为业务实现的,最合适的就是最好的。我们在深入的学习各种知识的目的并不是要将最前沿、最优秀的技术应用到所有的项目中,而是:将最合适的技术应用到项目中。

5. 总结

本文重点介绍了Hibernate及JPA中的条件查询。由条件入手讲到多条件,接下来介绍了更新及删除操作,最后与HQL进行简短的对比。同时,如果你想了解更多的关于本文所提到的技术细节,请下载我们为本文准备的可运行、测试的代码示例。