.in方法的用途
Criteria意为“标准、准则”,在数据库中翻译为“查询条件”,所以CriteriaBuider就是Java提供的、用来生成查询条件的“标准生成器”。
Criteria的in方法对应SOL语句中的IN关键字。
比如,
// 在student表中查询所有班级id为1或2的学生
SELECT * FROM `student` WHERE klass_id IN (1, 2);
因此in的作用就是:判断一条记录的某个值,是否在给定的数组里面。
上面的代码中,给定了1,2两个班级,那么在所有学生中,只要学生所在的班级是1或2中的任何一个,这个学生都会被查出来。
.in的用法
在baeldung中有比较详细的教程,但原文还是不够通俗。
接下来我把文章翻译为中文,把代码处理的更易懂一些。
in( )方法接受一个Expression并返回CriteriaBuilder.In类型的新Predicate。 它可用于测试给定表达式是否包含在值列表中。
/**
* 单词解释:
* criteria: 标准
* criteriaQuary: 标准查询
* criteriaBuider: 标准生成器
* clause: 规则、法则
*
* @Input 一个课程数组klasses
*/
// 构建一个标准查询,这个criteriaQuery是查询主体,用它来发起“查询”这个动作
CriteriaQuery<Student> criteriaQuery =
criteriaBuilder.createQuery(Student.class);
// root是需要查询的表,代码中的root基于Student,所以指的是“学生”表
Root<Student> root = criteriaQuery.from(Student.class);
// 使用buider构建一个in的查询规则,in()的参数是被查询的对象中的某个属性(比如学生的班级)
// 稍后作比较时,就用这个属性(学生的班级)和数组(传入的一组班级)做对比
In<Klass> inClause = criteriaBuilder.in(root.get("klass"));
// klasses是传入的数组,通过循环,把数组中的每个值依次添加到in的查询规则中
// 比如,klasses有1,2两个班,那么in查询规则就包含这两个班
for (String klass : klasses) {
inClause.value(klass);
}
// 通过“标准查询”,对学生表,发起“查询”操作,查询条件为in查询规则
criteriaQuery.select(root).where(inClause);
到此就明白了in的基本用法,然而,项目使用的是JPA,而且已经有了其他查询条件,如何把这种原生查询的写法,改写成适用于JPA的写法,从而拼接到已有的代码中,就是个问题,因此我继续探究。
Spring Data JPA中的specification
在Java后端的综合查询中,我们一定会经常与JPA打交道,比如Spring Data JPA。
在之前学习的时候,在教程的4.6.3 综合查询中就学习了如何在Repository中进行条件查询。
specification译为规范、说明书。
在Repository中,我们只需要写好Specification,然后传到仓库的方法中,仓库就会按照传入的的查询条件进行查询。
例如,先用CriteriaBuider写一个查询条件:
/**
* 学生查询条件
*/
public class StudentSpecs {
/**
* 属于某个班级
* @param klass 班级
*/
public static Specification<Student> belongToKlass(Klass klass) {
return (Specification<Student>) (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.equal(root.get("klass").as(Klass.class), klass);
}
}
然后在仓库层findAll方法中加入这个查询条件:
default Page findAll(Klass klass, @NotNull Pageable pageable) {
Specification<Student> specification = StudentSpecs.belongToKlass(klass);
return this.findAll(specification, pageable);
}
这种方式与上面讲到的原生查询方式相比,优势在于剥离了查询的“方法”和其中的查询“条件”:
- 定义查询条件时,只需要单独维护、测试这一个条件即可
- 实际查询时,可以把现有的查询条件灵活组合
区别
通过对比,原生的查询方法,是由标准查询criteriaQuery发起的查询操作。
而在JPA中,我们通过标准构造器criteriaBuilder生成查询条件Specification,然后传给仓库即可,这是写法上最明显的区别。
// 生成查询条件的语句
return (Specification<Student>) (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.equal(root.get("klass").as(Klass.class), klass);
在上述代码中,返回值类型是Specification查询条件,这里相当于写了一个匿名函数,传入了原生查询中的三个重要参数:查询表名root、标准查询criteriaQuery、标准构造器criteriaBuilder。
匿名函数调用了.equal方法,用来判断第一个参数和第二个参数是否相等,方法介绍如下:
javax.persistence.criteria.Predicate equal(javax.persistence.criteria.Expression<?> expression, javax.persistence.criteria.Expression<?> expression1);
in方法在Spring Data JPA中的特殊用法
探究过程(可略过)
由于已经学过.equal方法,因此很容易产生惯性思维:.equal方法是两个参数,方法的作用是比较参数一和参数二。
那么我猜:.in方法应该也是传入两个参数,一个Expression表达式和一个List数组,只要表达式的对象属于后面的数组,就把这条记录查询出来。
事实上我猜错了:.in方法只有一个参数
<T> javax.persistence.criteria.CriteriaBuilder.In<T> in(javax.persistence.criteria.Expression<? extends T> expression);
因此我陷入了懵B,这和剧本不太一样啊,一个参数怎么比较呢?
最后在老师的指点下,终于找到了答案。
用法
先回来看原生查询中的用法:
- 新建标准查询query
- 用标准查询创建root
- 用buider构建查询规则clause
- 用query调用clause完成查询
对比JPA中的用法
- 新建查询条件Specification
- 执行匿名函数,传入原生查询中的三个参数
- 直接返回用buider构建的查询条件
所以理解用法的关键在于明白这一点:JPA查询中返回的内容,实质上就是原生查询中的clause!!
接下来再把.equal和.in对比:
// 生成.equal查询条件的语句
return (Specification<Student>) (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.equal(root.get("klass").as(Klass.class), klass);
.equal在项目中是直接return了一个匿名函数,如果想把.in也写成这种写法,如何操作呢?
因为.in需要手动向数组添加值,因此无法一步完成,所以首先把匿名箭头函数变成花括号的形式:
// 生成.equal查询条件的语句
return (Specification<Student>) (root, criteriaQuery, criteriaBuilder) -> {
// todo 方法内容
};
接下来原封不动的把Bealdung的示例代码复制进去:
// 生成.equal查询条件的语句
return (Specification<Student>) (root, criteriaQuery, criteriaBuilder) -> {
// 构建一个标准查询,这个criteriaQuery是查询主体,用它来发起“查询”这个动作
CriteriaQuery<Student> criteriaQuery =
criteriaBuilder.createQuery(Student.class);
// root是需要查询的表,代码中的root基于Student,所以指的是“学生”表
Root<Student> root = criteriaQuery.from(Student.class);
// 使用buider构建一个in的查询规则,in()的参数是被查询的对象中的某个属性(比如学生的班级)
// 稍后作比较时,就用这个属性(学生的班级)和数组(传入的一组班级)做对比
In<Klass> inClause = criteriaBuilder.in(root.get("klass"));
// klasses是传入的数组,通过循环,把数组中的每个值依次添加到in的查询规则中
// 比如,klasses有1,2两个班,那么in查询规则就包含这两个班
for (String klass : klasses) {
inClause.value(klass);
}
// 通过“标准查询”,对学生表,发起“查询”操作,查询条件为in查询规则
criteriaQuery.select(root).where(inClause);
};
这时候必然会报错(返回值类型错误),然后再运用刚才的理论:不再使用query完成查询,而是直接把clause返回,从而可以传递给仓库来处理:
// 生成.equal查询条件的语句
return (Specification<Student>) (root, criteriaQuery, criteriaBuilder) {
// 构建一个标准查询,这个criteriaQuery是查询主体,用它来发起“查询”这个动作
CriteriaQuery<Student> criteriaQuery =
criteriaBuilder.createQuery(Student.class);
// root是需要查询的表,代码中的root基于Student,所以指的是“学生”表
Root<Student> root = criteriaQuery.from(Student.class);
// 使用buider构建一个in的查询规则,in()的参数是被查询的对象中的某个属性(比如学生的班级)
// 稍后作比较时,就用这个属性(学生的班级)和数组(传入的一组班级)做对比
In<Klass> inClause = criteriaBuilder.in(root.get("klass"));
// klasses是传入的数组,通过循环,把数组中的每个值依次添加到in的查询规则中
// 比如,klasses有1,2两个班,那么in查询规则就包含这两个班
for (String klass : klasses) {
inClause.value(klass);
}
// 直接返回查询规则即可
return inClause;
};
报错消失,为什么呢?因为JPA的Specification和criteriaBuilder生成的Clause本质上就是一个东西,所以返回值类型是兼容的。
到此就解决了SpringDataJPA使用criteriaQuery.in的写法不同。
版权声明
本文作者:河北工业大学梦云智开发团队 - 刘宇轩
新人经验不足,有建议欢迎交流,有错误欢迎轻喷
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。