从零开始 Spring Boot 69:JPA 条件查询

从零开始 Spring Boot 69:JPA 条件查询

从零开始 Spring Boot 69:JPA 条件查询_第1张图片

图源:简书 (jianshu.com)

在之前的文章中我们学习过条件查询(Criterial Query),构建条件查询的一般步骤是:

  1. 获取HibernateCriteriaBuilder
  2. 利用HibernateCriteriaBuilder创建JpaCriteriaQuery
  3. 利用JpaCriteriaQuery获取查询的根
  4. 利用HibernateCriteriaBuilder构建谓词
  5. 用谓词组装JpaCriteriaQuery
  6. 利用JpaCriteriaQuery创建Query并执行查询

本篇文章将进一步说明构建条件查询的细节问题。

本文的测试用例将使用下面的实体类:

@ToString
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class Student {
    public enum Level {
        FRESH_MAN, SOPHOMORE, JUNIOR, SENIOR
    }

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NotBlank
    @NotNull
    @Length(max = 45)
    @Column(unique = true)
    private String name;

    @Min(0)
    @Max(100)
    @NotNull
    private Integer averageScore;

    @Enumerated(EnumType.STRING)
    @NotNull
    private Level level;
}

先看一个简单的示例——查找所有的一年级学生:

var cb = sessionFactory.getCriteriaBuilder();
JpaCriteriaQuery<Student> query = cb.createQuery(Student.class);
JpaRoot<Student> root = query.from(Student.class);
JpaPredicate level = cb.equal(root.get("level"), Student.Level.FRESH_MAN);
query = query.where(level);
var students = session.createQuery(query).getResultList();

在这个示例中,用cb.equal构建了一个类似 JPQL 中的s.level=:level这样的查询条件,在这里被称作谓词(Predicate),其类型是JpaPredicate

谓词可以通过JpaCriteriaQuery.where方法绑定到条件查询上。

条件语句

可以用一系列谓词(Predicate)构建查询条件,下面会介绍一些常见的条件谓词。

and

可以用多个谓词组合成更复杂的查询条件,比如下面这个:

var cb = sessionFactory.getCriteriaBuilder();
JpaCriteriaQuery<Student> query = cb.createQuery(Student.class);
JpaRoot<Student> root = query.from(Student.class);
JpaPredicate level = cb.equal(root.get("level"), Student.Level.FRESH_MAN);
JpaPredicate averageScore = cb.ge(root.get("averageScore"), 60);
JpaPredicate and = cb.and(level, averageScore);
query = query.where(and);
var students = session.createQuery(query).getResultList();

这里用了三个谓词,组合成了一个类似 JPQL 中的s.level=:level and s.averageScore>=:score这样的查询条件,用于查询一年级平均分为60分以上的学生。

谓词中的ge表示 greater equal(大于等于),gt表示 greater than(大于),lelt类似。

实际使用中更多的是用级联调用和表达式,并不会为每个谓词创建本地变量,这样可读性会更好:

var cb = sessionFactory.getCriteriaBuilder();
JpaCriteriaQuery<Student> query = cb.createQuery(Student.class);
JpaRoot<Student> root = query.from(Student.class);
query = query.where(cb.and(
    cb.in(root.get("level"), List.of(Student.Level.FRESH_MAN, Student.Level.SOPHOMORE)),
    cb.ge(root.get("averageScore"), 60))
                   );
var students = session.createQuery(query).getResultList();

这个示例查询一年级和二年级所有分数大于等于60分的学生。

or

当然,条件语句并非只有一种写法,比如可以用or来替换in

var cb = sessionFactory.getCriteriaBuilder();
JpaCriteriaQuery<Student> query = cb.createQuery(Student.class);
JpaRoot<Student> root = query.from(Student.class);
query = query.where(cb.and(
    cb.or(
        cb.equal(root.get("level"), Student.Level.FRESH_MAN),
        cb.equal(root.get("level"), Student.Level.SOPHOMORE)),
    cb.ge(root.get("averageScore"), 60))
                   );
var students = session.createQuery(query).getResultList();

和上边的示例具有相同的功能。

between

一般如果我们要查询某个区间的结果,可能会这样组合使用谓词:

var cb = sessionFactory.getCriteriaBuilder();
JpaCriteriaQuery<Student> query = cb.createQuery(Student.class);
JpaRoot<Student> root = query.from(Student.class);
query = query.where(cb.and(cb.ge(root.get("averageScore"), 20),
                           cb.le(root.get("averageScore"), 80)));
var students = session.createQuery(query).getResultList();

JPA 生成的 SQL 语句为:

select s1_0.id,s1_0.average_score,s1_0.level,s1_0.name from student s1_0 where s1_0.average_score>=? and s1_0.average_score<=?

可以使用 between 谓词起到类似的效果:

var cb = sessionFactory.getCriteriaBuilder();
JpaCriteriaQuery<Student> query = cb.createQuery(Student.class);
JpaRoot<Student> root = query.from(Student.class);
query = query.where(cb.between(root.get("averageScore"), 20, 80));
var students = session.createQuery(query).getResultList();

JPA 生成的 SQL 语句:

select s1_0.id,s1_0.average_score,s1_0.level,s1_0.name from student s1_0 where s1_0.average_score between ? and ?

SQL between 操作符的相关介绍说明可以阅读这里。

like

like 也是常用的 SQL 操作符,对应 like 谓词:

var cb = sessionFactory.getCriteriaBuilder();
JpaCriteriaQuery<Student> query = cb.createQuery(Student.class);
JpaRoot<Student> root = query.from(Student.class);
query = query.where(cb.like(root.get("name"), "B%"));
var students = session.createQuery(query).getResultList();

JPA 生成的 SQL 语句:

select s1_0.id,s1_0.average_score,s1_0.level,s1_0.name from student s1_0 where s1_0.name like replace(?,'\\','\\\\')

in

使用 in 可以检索字段是否在某个范围内:

query = query.where(cb.in(root.get("level"), List.of(Student.Level.FRESH_MAN, Student.Level.SOPHOMORE)));

还有另一种写法:

query = query.where(root.get("level").in(Student.Level.FRESH_MAN, Student.Level.SOPHOMORE));

这种写法更接近 SQL 的风格。

isNull

如果字段可以为null,就需要用isNullisNotNull创建查询条件:

query = query.where(cb.isNull(root.get("name")));

JPA 生成的 SQL:

select s1_0.id,s1_0.average_score,s1_0.level,s1_0.name from student s1_0 where s1_0.name is null

isNotNull与之类似,这里不再赘述。

也可以用另一种风格的写法:

query = query.where(root.get("name").isNull());

排序

可以给条件查询添加排序:

query = query.orderBy(cb.desc(root.get("averageScore")),
cb.asc(root.get("name")));

生成的 SQL:

select s1_0.id,s1_0.average_score,s1_0.level,s1_0.name from student s1_0 order by s1_0.average_score desc,s1_0.name

聚合函数

条件语句同样可以使用聚合函数。

比如用count查询数据条目:

var cb = sessionFactory.getCriteriaBuilder();
JpaCriteriaQuery<Long> query = cb.createQuery(Long.class);
JpaRoot<Student> root = query.from(Student.class);
query.select(cb.count(root));
Long count = session.createQuery(query).getSingleResult();
System.out.println(count);

注意,这里的检索结果要映射到一个Long类型的变量,所以cb.createQuery的参数是Long.class,而非一般的Student.class

JPA 生成的 SQL:

select count(s1_0.id) from student s1_0

JPA 生成的 SQL 符合我们一般的编写 SQL 习惯——对主键字段进行计数。

再比如计算平均分总和:

var cb = sessionFactory.getCriteriaBuilder();
JpaCriteriaQuery<Integer> query = cb.createQuery(Integer.class);
JpaRoot<Student> root = query.from(Student.class);
query.select(cb.sum(root.get("averageScore")));
Integer sum = session.createQuery(query).getSingleResult();
System.out.println(sum);

CriteriaUpdate

可以创建一个CriteriaUpdate来构建UPDATE语句,并更新数据:

HibernateCriteriaBuilder cb = sessionFactory.getCriteriaBuilder();
JpaCriteriaUpdate<Student> criteriaUpdate = cb.createCriteriaUpdate(Student.class);
Root<Student> root = criteriaUpdate.from(Student.class);
criteriaUpdate.set("averageScore", 99);
criteriaUpdate.where(cb.equal(root.get("name"), "icexmoon"));
Transaction transaction = session.beginTransaction();
session.createMutationQuery(criteriaUpdate).executeUpdate();
transaction.commit();

JPA 生成的 SQL:

update student set average_score=? where name=?

CriteriaDelete

可以创建一个CriteriaDelete来构建DELETE语句,用于删除数据:

HibernateCriteriaBuilder cb = sessionFactory.getCriteriaBuilder();
JpaCriteriaDelete<Student> criteriaDelete = cb.createCriteriaDelete(Student.class);
Root<Student> root = criteriaDelete.from(Student.class);
criteriaDelete.where(cb.le(root.get("averageScore"), 60));
Transaction transaction = session.beginTransaction();
session.createMutationQuery(criteriaDelete).executeUpdate();
transaction.commit();

The End,谢谢阅读。

可以从这里获取本文的完整示例代码。

参考资料

  • Combining JPA And/Or Criteria Predicates | Baeldung
  • SQL BETWEEN 操作符 | 菜鸟教程 (runoob.com)

你可能感兴趣的:(JAVA,spring,boot,jpa,predicate)