这是一个小引言:大四小白实习生,前几天看了公司的代码之后发现,用的与数据库交互的技术不是常见的SSM中的MyBatis而用的是SpringDataJPA,相对来说更加简洁一点吧,注释一键生成数据库表,不用在xml中写sql,之前做过一些小Demo都是实现的都只是继承JpaRepository接口,然后看了下代码看到竟然除了继承JpaRepository接口还继承了一个叫JpaSpecificationExecutor的接口,这个接口怎么用的呢?然后就看源码+面向百度一顿搜,看了很多都一知半解,接下来我来做一个学习笔记,方便一下跟我一样完全不懂的小白入门(以防自己忘了)。
为什么要用JpaSpecificationExecutor?
首先我们看回我们相对熟悉的JpaRepository,继承这个接口可以实现一些这个接口已经给我们提供好的方法对数据库进行查询,那在我们工作之中,用这些接口中的方法查询显然是不够的,比如说,我们是校长,今天我们想查大二一共有多少女生,条件就是(where后面的):年级=大二 and 性别=女生;然后明天,善变的校长又想来查询一下商学院有多少女生,那条件就是:学系=商学院 +性别=女生;那善变的校长一会查这个一会查那个,那我们总不能先预先写好那么多SQL吧?好了,这时候就是要发挥JpaSpecificationExecutor接口的功能了,那就是动态查询。
初步认识JpaSpecificationExecutor
直接点进去简单看一下:
public interface JpaSpecificationExecutor<T> {
Optional<T> findOne(@Nullable Specification<T> var1);
List<T> findAll(@Nullable Specification<T> var1);
Page<T> findAll(@Nullable Specification<T> var1, Pageable var2);
List<T> findAll(@Nullable Specification<T> var1, Sort var2);
long count(@Nullable Specification<T> var1);
}
跟我们之前操作的JpaRepository有点像,不过这里值得注意的是,接口中定义的每一个方法都有一个Specification< T >参数,这个是什么呢,因为JpaSpecificationExecutor是为了让我们获得动态查询的功能,所谓动态查询就是可以根据业务逻辑随时改变查询的条件,Specification就是代表查询的条件,我们可以动态去拼接不同的查询条件,用调用这里的方法的形式来实现我们的查询,只需要把查询条件以参数的形式(Specification)传递过来就可以了。
JpaSpecificationExecutor的方法
接下来我们来看一下JpaSpecificationExecutor接口里面定义的方法是怎么用的:
public interface JpaSpecificationExecutor<T> {
//查询单个对象
Optional<T> findOne(@Nullable Specification<T> var1);
//查询列表
List<T> findAll(@Nullable Specification<T> var1);
//查询全部(分页)
//Pageable:分页参数
//返回值:Page(springdatajpa提供的)
Page<T> findAll(@Nullable Specification<T> var1, Pageable var2);
//查询列表
//Sort排序参数
List<T> findAll(@Nullable Specification<T> var1, Sort var2);
//统计查询
long count(@Nullable Specification<T> var1);
}
大概了解了一下JpaSpecificationExecutor接口定义的各个方法的作用之后,我们来看下最核心的参数Specification的是什么,Specification是一个接口,也代表我们的查询条件,我们通过实现这个接口来自定义我们的查询条件:
Specification<T> spec = new Specification<T>() {
@Override
public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
return null;
}
}
这里是通过一个匿名内部类的方式实现这个接口,我们来可以看到,这个接口中给我们提供了一个toPredicate方法,我们来解释一下这个方法的参数:
- Root:查询的根对象(查询的任何属性都可以从根对象中获取)
- CriteriaQuery:顶层查询对象,自定义查询方式(暂时不做了解)
- CriteriaBuilder:查询的构造器,封装了很多的查询条件
简单代码实现
我们马上开始操作:
@Repository
public interface TestDao extends JpaSpecificationExecutor<TestPO>,JpaRepository<TestPO,String> {
}
我们先定义一个TestDao的接口,让它继承我们所需的两个接口:
- JpaRepository<操作的实体,实体中主键的类型>:让我们的接口具有基本的JPA查询方法
- JpaSpecificationExecutor<操作的实体>:具有动态查询的功能
再来业务层中调用TestDao接口中方法:
//截取一部分代码
@Autowired
TestDao testDao;
@Override
public void testSpec() {
//匿名内部类返回一个Specification传给下面的findOne
Specification<Student> specification = new Specification<Student>() {
@Override
//自定义我们所需的查询条件
public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder criteriaBuilder) {
return null;
}
};
//注意是调用JpaSpecificationExecutor接口中的:
//Optional<T> findOne(@Nullable Specification<T> spec);
testDao.findOne(specification);
}
- 实现Specification<查询对象的类型>接口(这里使用匿名内部类)
- 实现toPredicate方法,构造查询条件,toPredicate方法参数的两个参数(这里先不了解CriteriaQuery)
public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder criteriaBuilder)
- root:获取需要查询对象属性
- CriteriaBuilder:构造查询条件;比如说like模糊查询等
然后我们正式结合我们所需的功能来实现,接着补充上面的代码:
//截取一部分代码
@Autowired
TestDao testDao;
@Override
public void testSpec() {
//匿名内部类返回一个Specification传给下面的findOne
Specification<Student> specification = new Specification<Student>() {
@Override
//自定义我们所需的查询条件
public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder criteriaBuilder) {
//1.获得需要查询的属性
Path<String> major = root.get("major");
//2.构造查询条件
//注意这里的equal是指的SQL查询语句的=
//第一个参数:需要查询的属性 第二个参数:我们想要查询的属性值
Predicate predicates = criteriaBuilder.equal(major,"商学院");
//3.将查询条件Predicate返回
return predicates;
}
};
//注意是调用JpaSpecificationExecutor接口中的:
//Optional<T> findOne(@Nullable Specification<T> spec);
testDao.findOne(specification);
}
结合注释与代码知道,我们做的查询的SQL是:select * from student where major = ‘商学院’;
没有任何问题,接下来我们难度升级,进行多条件查询;
多条件拼接查询
我们回到最初的概述,善变的校长这时候想要查询,即是女生又是商学院的同学了,这时就需要两个条件:女生 与 商学院,我们来上代码:
//截取一部分代码
@Autowired
TestDao testDao;
@Override
public void testSpec() {
//匿名内部类返回一个Specification传给下面的findOne
Specification<Student> specification = new Specification<Student>() {
@Override
//自定义我们所需的查询条件
public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder criteriaBuilder) {
//1.获得需要查询的属性
Path<String> major = root.get("major");
Path<String> major = root.get("male");
//2.构造查询条件
//注意这里的equal是指的SQL查询语句的=
//第一个参数:需要查询的属性 第二个参数:我们想要查询的属性值
Predicate predicates1 = criteriaBuilder.equal(major,"商学院");
Predicate predicates2 = criteriaBuilder.equal(male,"女");
//3.将两个条件组合起来
//这里又用到了criteriaBuilder的方法 and表示同时满足 与条件
Predicate predicates3 = criteriaBuilder.and(predicates1,predicates2)
//4.将查询条件Predicate3返回
return predicates3;
}
};
//注意是调用JpaSpecificationExecutor接口中的:
//Optional<T> findOne(@Nullable Specification<T> spec);
testDao.findOne(specification);
}
分页查询小案例
看到这里应该明白了JpaSpecificationExecutor的用法了吧,还有剩下的排序分页其实也是一样的,这里贴一个我在工作中写的代码,用到了Java8的新特性:
@Override
public Page<TestPO> page(TestPO po, Integer pageNumber, Integer pageSize) {
Pageable pageable = PageRequest.of(pageNumber, pageSize);
Specification<TestPO> spec = (Specification<TestPO>) (root, query, cb) -> {
Predicate predicates = null;
if (StringUtils.isNotBlank(po.getModelName())) {
Path<String> cnName = root.get("modelName");
predicates = cb.like(cnName, "%" + po.getModelName() + "%");
}
return predicates;
};
return testDao.findAll(spec, pageable);
}