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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM