.findAll((root, query, cb) -> { ;JpaSpecificationExecutor

JpaSpecificationExecutor 介绍 [自用]

JpaSpecificationExecutor 是 JPA 2.0 提供的 Criteria API 的使用封装,可以用于动态生成 Query 来满足我们业务中的各种复杂场景。Spring Data JPA 为我们提供了 JpaSpecificationExecutor 接口,只要简单实现 toPredicate 方法就可以实现复杂的查询。

JpaSpecificationExecutor接口

public interface JpaSpecificationExecutor<T> {
	//根据 Specification 条件查询单个对象
    T findOne(Specification<T> var1);
	//根据 Specification 条件查询一个list集合
    List<T> findAll(Specification<T> var1);
	//根据 Specification 条件,分页查询
    Page<T> findAll(Specification<T> var1, Pageable var2);
	//根据 Specification 条件,带排序的查询结果
    List<T> findAll(Specification<T> var1, Sort var2);
	//根据 Specification 条件,查询条数
    long count(Specification<T> var1);
}

查看 Specifications 源码

public interface Specification<T> {
    Predicate toPredicate(Root<T> var1, CriteriaQuery<?> var2, CriteriaBuilder var3);
}

从这里可以看出,每个调用的地方都需要,创建 Specification 的实现类,而 JpaSpecificationExecutor 是针对 Criteria API 进行了 predicate 标准封装,帮我们封装了通过 EntityManager 的查询和使用细节,使操作 Criteria 更加便利了一些。所以我们要掌握一下 Predicate、Root、CriteriaQuery、CriteriaBuilder 是什么?

Criteria概念的简单介绍

1.Root
代表了可以查询和操作的实体对象的根,如果将实体对象比喻成表名,那 root 里面就是这张表里面的字段,这不过是 JPQL 的实体字段而已。通过里面的 Path get(String attributeName),来获得我们想操作的字段。

2.CriteriaQuery<?>query
代表一个specific的顶层查询对象,它包含着查询的各个部分,比如:select、from、where、group by、order by等。CriteriaQuery对象只对实体类型或嵌入式类型的Criteria查询起作用,简单理解,它提供了查询ROOT的方法。常用的方法有:

CriteriaQuery<T> where(Predicate... restrictions);
CriteriaQuery<T> select(Selection<? extends T> selection);
CriteriaQuery<T> having(Predicate... restrictions);

3.CriteriaBuilder cb
用来构建 CritiaQuery 的构建器对象,其实就相当于条件或者是条件组合,并以 Predicate 的形式返回。下面是构建简单的 Predicate 示例:

简单来说

Root:查询哪个表
CriteriaQuery:查询哪些字段,排序是什么
CriteriaBuilder:字段之间是什么关系,如何生成一个查询条件,每一个查询条件都是什么方式
Predicate(Expression):单独每一条查询条件的详细描述

JpaSpecificationExecutor 使用案例

新建两个实体

@Entity(name = "UserInfoEntity")
@Table(name = "user_info", schema = "test")
public class UserInfoEntity  implements Serializable {
   @Id
   @Column(name = "id", nullable = false)
   private Integer id;
   @Column(name = "first_name", nullable = true, length = 100)
   private String firstName;
   @Column(name = "last_name", nullable = true, length = 100)
   private String lastName;
   @Column(name = "telephone", nullable = true, length = 100)
   private String telephone;
   @Column(name = "create_time", nullable = true)
   private Date createTime;
   @Column(name = "version", nullable = true)
   private String version;
   @OneToOne(optional = false,fetch = FetchType.EAGER)
   @JoinColumn(referencedColumnName = "id",name = "address_id",nullable = false)
   @Fetch(FetchMode.JOIN)
   private UserReceivingAddressEntity addressEntity;
......
}
@Entity
@Table(name = "user_receiving_address", schema = "test")
public class UserReceivingAddressEntity  implements Serializable {
   @Id
   @Column(name = "id", nullable = false)
   private Integer id;
   @Column(name = "user_id", nullable = false)
   private Integer userId;
   @Column(name = "address_city", nullable = true, length = 500)
   private String addressCity;
......
}

UserRepository 需要继承 JpaSpecificationExecutor

public interface UserRepository extends JpaSpecificationExecutor<UserInfoEntity> {
}

调用 UserInfoManager 的写法
我们演示一下直接用 lambda 使用 Root 和 CriteriaBuilder 做一个简单的不同条件的查询和链表查询。

@Component
public class UserInfoManager {
   @Autowired
   private UserRepository userRepository;
   public Page<UserInfoEntity> findByCondition(UserInfoRequest userParam,Pageable pageable){
      return userRepository.findAll((root, query, cb) -> {
         List<Predicate> predicates = new ArrayList<Predicate>();
         if (StringUtils.isNoneBlank(userParam.getFirstName())){
            //liked的查询条件
            predicates.add(cb.like(root.get("firstName"),"%"+userParam.getFirstName()+"%"));
         }
         if (StringUtils.isNoneBlank(userParam.getTelephone())){
            //equal查询条件
            predicates.add(cb.equal(root.get("telephone"),userParam.getTelephone()));
         }
         if (StringUtils.isNoneBlank(userParam.getVersion())){
            //greaterThan大于等于查询条件
            predicates.add(cb.greaterThan(root.get("version"),userParam.getVersion()));
         }
         if (userParam.getBeginCreateTime()!=null&&userParam.getEndCreateTime()!=null){
            //根据时间区间去查询   predicates.add(cb.between(root.get("createTime"),userParam.getBeginCreateTime(),userParam.getEndCreateTime()));
         }
         if (StringUtils.isNotBlank(userParam.getAddressCity())) {
            //联表查询,利用root的join方法,根据关联关系表里面的字段进行查询。
            predicates.add(cb.equal(root.join("addressEntityList").get("addressCity"), userParam.getAddressCity()));
         }
         return query.where(predicates.toArray(new Predicate[predicates.size()])).getRestriction();
      }, pageable);
   }
}

我们再来看一个不常见的复杂查询的写法,来展示一下 CriteriaQuery 的用法(强烈不推荐哦,和上面比起来太不优雅了

public List<MessageRequest> findByConditions(String name, Integer price, Integer stock) {  
        messageRequestRepository.findAll((Specification<MessageRequest>) (Root, query, criteriaBuilder) -> {
            //这里用 List 存放多种查询条件,实现动态查询
            List<Predicate> predicatesList = new ArrayList<>();
            //name 模糊查询,like 语句
            if (name != null) {predicatesList.add(criteriaBuilder.and(criteriaBuilder.like(Root.get("name"),"%" + name + "%")));}
            // itemPrice 小于等于 <= 语句
            if (price != null) {predicatesList.add(criteriaBuilder.and(criteriaBuilder.le(Root.get("price"), price)));}
            //itemStock 大于等于 >= 语句
            if (stock != null) {predicatesList.add(criteriaBuilder.and(criteriaBuilder.ge(Root.get("stock"), stock)));}
            //where() 拼接查询条件
            query.where(predicatesList.toArray(new Predicate[predicatesList.size()]));
            //返回通过 CriteriaQuery 拼装的 Predicate
            return query.getRestriction();
        });

Specification 工作中的一些扩展

我们在实际工作中会发现,如果上面的逻辑,简单重复写总感觉是不是可以抽出一些公用方法呢,此时引入一种工厂模式,帮我们做一些事情,可以让代码更加优雅。基于 JpaSpecificationExecutor 的思路,我们创建一个 SpecificationFactory.Java 内容如下:

public final class SpecificationFactory {
   /**
    * 模糊查询,匹配对应字段
    */
   public static Specification containsLike(String attribute, String value) {
      return (root, query, cb)-> cb.like(root.get(attribute), "%" + value + "%");
   }
   /**
    * 某字段的值等于 value 的查询条件
    */
   public static Specification equal(String attribute, Object value) {
      return (root, query, cb) -> cb.equal(root.get(attribute),value);
   }
   /**
    * 获取对应属性的值所在区间
    */
   public static Specification isBetween(String attribute, int min, int max) {
      return (root, query, cb) -> cb.between(root.get(attribute), min, max);
   }
   public static Specification isBetween(String attribute, double min, double max) {
      return (root, query, cb) -> cb.between(root.get(attribute), min, max);
   }
   public static Specification isBetween(String attribute, Date min, Date max) {
      return (root, query, cb) -> cb.between(root.get(attribute), min, max);
   }
   /**
    * 通过属性名和集合实现 in 查询
    */
   public static Specification in(String attribute, Collection c) {
      return (root, query, cb) ->root.get(attribute).in(c);
   }
   /**
    * 通过属性名构建大于等于 Value 的查询条件
    */
   public static Specification greaterThan(String attribute, BigDecimal value) {
      return (root, query, cb) ->cb.greaterThan(root.get(attribute),value);
   }
   public static Specification greaterThan(String attribute, Long value) {
      return (root, query, cb) ->cb.greaterThan(root.get(attribute),value);
   }
......
}

PS:可以根据实际工作需要和场景进行不断扩充

调用实例1:

userRepository.findAll(
      SpecificationFactory.containsLike("firstName", userParam.getLastName()),
      pageable);

配合 Specifications 使用,调用实例2:

userRepository.findAll(Specifications.where(
      SpecificationFactory.containsLike("firstName", userParam.getLastName()))
            .and(SpecificationFactory.greaterThan("version",userParam.getVersion())),
      pageable);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值