💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
[TOC] # JpaSpecificationExecutor **用来做动态查询,可以实现带查询条件的分页** **不属于Repository体系,实现一组 JPA Criteria 查询相关的方法** 我们可以通过 AND 或者 OR 等连接词来不断拼接属性来构建多条件查询, 但如果参数⼤于 6 个时,⽅法名就会变得⾮常的长,并且还不能解决动态多条件查询的场景。 到这⾥就需要给⼤家介绍另外⼀个利器 JpaSpecificationExecutor 了 JpaSpecificationExecutor 是 JPA 2.0 提供的 Criteria API 的使⽤封装,可以⽤于动态⽣成 Query 来满⾜我们业务中的各种复杂场景。Spring Data JPA 为我们提供了 JpaSpecificationExecutor 接⼝,只要简单实现 toPredicate ⽅法就可以实现复杂的查询。 JpaSpecificationExecutor源码 ~~~ public interface JpaSpecificationExecutor<T> { /** 根据查询条件返回一个实体, 注意如果结果多余一条,则抛异常 */ T findOne(Specification<T> spec); /** 根据查询条件返回多个实体.*/ List<T> findAll(Specification<T> spec); /** 根据查询条件和分页参数,返回当前页的实体信息.*/ Page<T> findAll(Specification<T> spec, Pageable pageable); /*根据查询条件和排序规则,返回一个排序好的实体集合. */ List<T> findAll(Specification<T> spec, Sort sort); /** 根据查询条件统计实体的数量 */ long count(Specification<T> spec); } ~~~ JpaSpecificationExecutor 的源码很简单,根据 Specification 的查询条件返回 List、Page 或者 count 数据。 在使⽤ JpaSpecificationExecutor 构建复杂查询场景之前,我们需要了解⼏个概念 * `Root<T> root`,代表了可以查询和操作的实体对象的根,开⼀个通过 get("属性名") 来获取对应的值。 * `CriteriaQuery<?> query`,代表⼀个 specific 的顶层查询对象,它包含着查询的各个部分,⽐如 select 、from、where、group by、order by 等。 * `CriteriaBuilder cb`,来构建 CritiaQuery 的构建器对象,其实就相当于条件或者是条件组合,并以 Predicate 的形式返回。 # 例子 ## 持久化层 ~~~ @Repository public interface UserRepository extends JpaRepository<User, Long>, JpaSpecificationExecutor<User> { } ~~~ ## service业务层 ~~~ public interface UserService { //条件查询 id 范围在 [start,end] 之间的数据。如果 age 不为空,加上条件 List<User> findAll(Long start, Long end, String age); //查询 id 大于等于 start 的数据,且进行分页查询 Page<User> findAll(Long start, int page, int size); //模糊查询 like List<User> findAllLike(String name); } ~~~ ~~~ import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.jpa.domain.Specification; import org.springframework.stereotype.Service; import javax.annotation.Resource; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Predicate; import javax.persistence.criteria.Root; import java.util.ArrayList; import java.util.List; @Service public class UserServiceImpl implements UserService { @Resource private UserRepository userRepository; @Override public List<User> findAll(Long start, Long end, String age) { //直接使用匿名内部类实现接口 Specification<User> specification = new Specification<User>() { @Override public Predicate toPredicate(Root<User> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) { List<Predicate> predicateList = new ArrayList<>(); //条件1:查询 id 为 海信 的数据,root.get 中的值与 User 实体中的属性名称对应 if (age != null && !"".equals(age)) { predicateList.add(criteriaBuilder.equal(root.get("age").as(String.class), age)); } //条件2:User id(id)大于等于 start 的数据,root.get 中的 id 必须对应 User 中的属性 predicateList.add(criteriaBuilder.greaterThanOrEqualTo(root.get("id").as(Long.class), start)); //日期是 as(Date.class) //条件3:User id(id) 小于等于 end predicateList.add(criteriaBuilder.lessThanOrEqualTo(root.get("id").as(Long.class), end)); Predicate[] pre = new Predicate[predicateList.size()]; pre = predicateList.toArray(pre); return criteriaQuery.where(pre).getRestriction(); } }; return userRepository.findAll(specification); } @Override public Page<User> findAll(Long start, int page, int size) { page--; page = page < 0 ? 0 : page; //page 为页码,数据库从0页开始 //可以使用重载的 of(int page, int size, Sort sort) 方法指定排序字段 Pageable pageable = PageRequest.of(page, size); //创建查询规范 Specification<User> tvSpecification = new Specification<User>() { @Override public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) { List<Predicate> predicateList = new ArrayList<>(); //查询 id 在 start 与 end 之间的数据,闭区间 predicateList.add(cb.between(root.get("id").as(Long.class), start, 10L)); //数字可以自己传也可以 Predicate[] predicates = new Predicate[predicateList.size()]; return query.where(predicateList.toArray(predicates)).getRestriction(); } }; return userRepository.findAll(tvSpecification, pageable);//无数据时返回空列表 } @Override public List<User> findAllLike(String name) { Specification<User> tvSpecification = new Specification<User>() { @Override public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) { Predicate[] predicates = new Predicate[1]; //like(Expression<String> x, String pattern):参数 pattern 表示匹配的格式 predicates[0] = cb.like(root.get("name").as(String.class), "%" + name + "%"); //同理以 xxx 开头,则为 tvNameLike + "%" return query.where(predicates).getRestriction(); } }; //规范查询的同时,指定以主键 id 倒序排序 return userRepository.findAll(tvSpecification, Sort.by(Sort.Direction.DESC, "id")); } } ~~~ ## 测试 ~~~ List<User> users = userService.findAll(1L, 9L, "33"); ~~~ ~~~ int page = 2; int size = 4; //page = page == null ? 1 : page; //size = size == null ? 2 : size; Page<User> res = userService.findAll(1L, page, size); log.info("总记录数:" + res.getTotalElements()); log.info("总页数:" + res.getTotalPages()); List<User> users = res.getContent(); System.out.println(users); ~~~ ~~~ List<User> res = userService.findAllLike("a1"); System.out.println(res); ~~~