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(); // ❺ }
上述代码中我们主要完成了如下功能:
- 获取当前的Session实例。
- 调用Session中的getCriteriaBuilder()来获取一个CriteriaBuilder
- 调用CriteriaBuilder中的createQuery来获取一个CriteriaQuery
- 指定在查询结果中返回的数据项
- 根据CriteriaQuery创建查询并获取查询结果。
3.1 表达式Expressions
CriteriaBuilder可以创建特定的查询条件,我们把这些查询条件称为表达式Expressions。CriteriaQuery的where()方法可以接收表达式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进行简短的对比。同时,如果你想了解更多的关于本文所提到的技术细节,请下载我们为本文准备的可运行、测试的代码示例。