JPASpecificationExecutor 使用详解

可用于动态生成query,帮我们提供了一个高级的入口和结构,通过这个入口可以使用底层JPA的Criteria的所有方法,可以满足所有业务场景

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);
}

这个接口是围绕着Specification接口来定义的:

public interface Specification<T> extends Serializable {
    long serialVersionUID = 1L;

    static <T> Specification<T> not(@Nullable Specification<T> spec) {
        return spec == null ? (root, query, builder) -> {
            return null;
        } : (root, query, builder) -> {
            return builder.not(spec.toPredicate(root, query, builder));
        };
    }

    static <T> Specification<T> where(@Nullable Specification<T> spec) {
        return spec == null ? (root, query, builder) -> {
            return null;
        } : spec;
    }

    default Specification<T> and(@Nullable Specification<T> other) {
        return SpecificationComposition.composed(this, other, CriteriaBuilder::and);
    }

    default Specification<T> or(@Nullable Specification<T> other) {
        return SpecificationComposition.composed(this, other, CriteriaBuilder::or);
    }

    @Nullable
    Predicate toPredicate(Root<T> var1, CriteriaQuery<?> var2, CriteriaBuilder var3);
}

需要子类是现实的主要方法是 toPredicate 

Root<T>  var1: 代表了可以查询和操作的实体对象的根,如果将实体对象比喻成表名,那root里面就是这张表里面的字段,是JPQL的实体字段,通过Path<Y>get(String var0)来获得操作的字段

 

CriteriaQuery<?> var2: 代表一个specific的顶层查询对象,它包含着查询的各个部分,如: select、form、where、group by、order by 等,它提供了查询var1的的方法,常用的有 where、select、having

 

CriteriaBuilder var3: 用来构建CriteriaQuery的构建器对象,其实就相当于条件或条件组合

 

示例:

@Test
public void testJspSpecification() {
    SystemUser systemUser = new SystemUser();
    systemUser.setUname("lis");

    Specification<SystemUser> systemUserSpecification = (root, query, cb) -> {
        List<Predicate> predicates = new ArrayList<>();
        //  模糊查询
        if (systemUser.getUname() != null && systemUser.getUname().length() > 0){
            predicates.add(cb.like(root.get("uname"),systemUser.getUname() + "%"));
        }

        //  日期比较
        if (systemUser.getCreateTime() != null){
            predicates.add(cb.greaterThanOrEqualTo(root.get("createTime").as(String.class),"2020-11-21 14:54:42"));
        }

        return query.where(predicates.toArray(new Predicate[predicates.size()])).getRestriction();
    };
}

 

Specification 的扩展

在实际使用中,动态条件通过不断创建Predicate对象设置参数完成,此时可以使用工厂模式帮我们做一些事情

public class SpecificationFactory {
    // 时区对象
    private static final ZoneOffset ZONE_OFFSET = ZoneOffset.of("+8");
    // 日期时间格式化对象
    private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

    /**
     * 模糊匹配头部, like %?1
     *
     * @param fieldName 实体中的字段名称
     * @param value     固定值
     * @return 查询条件的封装对象
     */
    public static Specification likeStart(String fieldName, String value) {
        return (root, query, cb) -> cb.like(root.get(fieldName), "%" + value);
    }

    /**
     * 模糊匹配尾部, like ?1%
     *
     * @param fieldName 实体中的字段名称
     * @param value     固定值
     * @return 查询条件的封装对象
     */
    public static Specification likeEnd(String fieldName, String value) {
        return (root, query, cb) -> cb.like(root.get(fieldName), value + "%");
    }

    /**
     * 完全模糊匹配 , like %?1%
     *
     * @param fieldName 实体中的字段名称
     * @param value     固定值
     * @return 查询条件的封装对象
     */
    private static Specification like(String fieldName, String value) {
        return likeBuild(fieldName, "%" + value + "%");
    }

    private static Specification likeBuild(String fieldName, String value) {
        return (root, query, cb) -> cb.like(root.get(fieldName), "%" + value + "%");
    }

    /**
     * 任意值相等比较
     *
     * @param fieldName 实体中的字段名称
     * @param value     比较值
     * @return 查询条件的封装对象
     */
    public static <T> Specification eq(String fieldName, T value) {
        return (root, query, cb) -> cb.equal(root.get(fieldName), value);
    }

    /**
     * 比较日期区间
     *
     * @param fieldName 实体中的字段名称
     * @param min       最小日期值
     * @param max       最大日期值
     * @return 查询条件的封装对象
     */
    public static Specification betweenDate(String fieldName, Date min, Date max) {
        LocalDateTime lmin = LocalDateTime.ofInstant(min.toInstant(), ZONE_OFFSET);
        LocalDateTime lmax = LocalDateTime.ofInstant(max.toInstant(), ZONE_OFFSET);
        return (root, query, cb) -> cb.between(root.get(fieldName).as(String.class), DATE_TIME_FORMATTER.format(lmin), DATE_TIME_FORMATTER.format(lmax));
    }

    /**
     * 比较任意值的区间
     *
     * @param fieldName 实体中的字段名称
     * @param min       最小值
     * @param max       最大值
     * @param <T>
     * @return 查询条件的封装对象
     */
    public static <T extends Comparable> Specification between(String fieldName, T min, T max) {
        return (root, query, cb) -> cb.between(root.get(fieldName), min, max);
    }


    /**
     * 数值大于比较
     *
     * @param fieldName 实体中的字段名称
     * @param value     比较值
     * @param <T>
     * @return 查询条件的封装对象
     */
    public static <T extends Number> Specification gt(String fieldName, T value) {
        return (root, query, cb) -> cb.gt(root.get(fieldName).as(Number.class), value);
    }


    /**
     * 数值大于等于比较
     *
     * @param fieldName 实体中的字段名称
     * @param value     比较值
     * @param <T>
     * @return 查询条件的封装对象
     */
    public static <T extends Comparable> Specification gte(String fieldName, T value) {
        return (root, query, cb) -> cb.greaterThanOrEqualTo(root.get(fieldName), value);
    }

    /**
     * 数值小于比较
     *
     * @param fieldName 实体中的字段名称
     * @param value     比较值
     * @param <T>
     * @return 查询条件的封装对象
     */
    public static <T extends Number> Specification lt(String fieldName, T value) {
        return (root, query, cb) -> cb.lt(root.get(fieldName).as(Number.class), value);
    }

    /**
     * 数值小于等于比较
     *
     * @param fieldName 实体中的字段名称
     * @param value     比较值
     * @param <T>
     * @return 查询条件的封装对象
     */
    public static <T extends Comparable> Specification lte(String fieldName, T value) {
        return (root, query, cb) -> cb.lessThanOrEqualTo(root.get(fieldName), value);
    }

    /**
     * 字段为null条件
     * @param fieldName 实体中的字段名称
     * @return 查询条件的封装对象
     */
    public static Specification isNull(String fieldName){
        return (root, query, cb) -> cb.isNull(root.get(fieldName));
    }

    /**
     * 字段不为null条件
     * @param fieldName 实体中的字段名称
     * @return 查询条件的封装对象
     */
    public static Specification isNotNull(String fieldName){
        return (root, query, cb) -> cb.isNotNull(root.get(fieldName));
    }

    /**
     * in 条件
     * @param fieldName
     * @param values
     * @return
     */
    public static Specification in(String fieldName,Object...values){
        return (root, query, cb) -> root.get(fieldName).in(values);
    }

}

用法:

@Test
public void testJspSpecification() {
    // 传值
    SystemUser systemUser = new SystemUser();
    systemUser.setUname("lis");
    systemUser.setEmail("@qq.com");
    // 构造条件
    Specification specification = SpecificationFactory.lt("createTime", new Date().getTime());
    specification = specification.and(SpecificationFactory.likeEnd("uname", systemUser.getUname()));
    specification = specification.and(SpecificationFactory.isNull("email"));
    // 打印结果
    List<SystemUser> systemUserList = this.userRepository.findAll(specification);
    for (SystemUser user : systemUserList) {
        System.out.println(user);
    }
}

 

posted @ 2020-11-21 16:26  半雨微凉  阅读(9324)  评论(0编辑  收藏  举报