SpringBoot中基於JPA的多條件動態查詢封裝

最近地攤經濟盛行了,對於wshanshi這個喫貨來說是再幸福不過的了。逛喫,逛喫,逛喫…怕是開心的要飛起來了呢!
要不咱也擺攤賣點啥去?看了看鏡子裏帥氣的面孔,笑容愈發…
“老闆,來一份wshanshi。酸甜,微辣,微辣…”
“好嘞!您稍等!我這就去把wshanshi叫來!”

在這裏插入圖片描述
“小二,來說一下SpringBoot中基於JPA的多條件動態查詢封裝。講不好不但不給飯錢,還要把你們wshanshi小姐姐賠給我!”
“好嘞客官,給您免單!趕緊把她帶走,帶走!把我喫窮了都!”

用過SpringDataJPA的都知道,自定義接口繼承JpaRepository<T,ID>接口、JpaSpecificationExecutor 接口之後,可以按照一定的規則定義接口方法名,實現變着花樣的增刪改查。

源碼如下:
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述
原理在於:自定義接口繼承了JpaRepository<T, ID>和JpaSpecificationExecutor 接口。由於JpaRepository<T, ID> 繼承了 PagingAndSortingRepository<T, ID>接口和QueryByExampleExecutor接口,且PagingAndSortingRepository<T, ID>又繼承了CrudRepository<T, ID>接口。因此可以直接使用默認封裝的增刪改查方法、分頁排序方法、多條件動態查詢方法。

當然你也可以自定義,像常見的根據用戶名查找用戶、根據用戶編號刪除用戶等等。
在這裏插入圖片描述
有興趣的可以去看一下源碼,這裏就不多說了。開始今天的主題:SpringBoot中基於JPA的多條件動態查詢封裝。
在這裏插入圖片描述
JPA中基於Specification的多條件動態查詢,Specification的使用點這裏:SpringDataJPA中使用Specification進行表連接多條件分頁動態查詢

像下圖這樣,使用Specification時,如果不進行二次封裝,那麼一個項目中可能會寫多個動態條件查詢方法。整體看起來代碼是冗餘的。
這個圖片中的代碼,雖然用法對,寫法也對,但是如果出現過多,不用想絕對會被Diss。別問我怎麼知道的,不信你嘗試。到時候一定要控制住你的右手,離自己的小臉遠一點,力度小一點。哇哈哈哈哈哈…
在這裏插入圖片描述
那麼,爲了避免衝動起來自己扇自己,有沒有更好的辦法,可以看起來既清爽簡潔,使用起來又方便快捷的?

當然,我們可以選擇封裝一下。自定義註解實現多條件動態查詢(可表關聯查詢),使用方便簡潔,清晰明瞭。何樂而不爲呢?
在這裏插入圖片描述
具體操作如下:

一、自定義註解查詢類
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Query {

    /**
     * 基本對象的屬性名
     */
    String propName() default "";

    /**
     * 基本對象的屬性名LIST OR
     */
    String[] orPropNames() default {};

    String orPropVal() default "";

    /**
     * 查詢方式 默認精確查詢
     */
    Type type() default Type.EQUAL;

    /**
     * 多表連接查詢的屬性名,如User類中的role
     *
     * @return
     */
    String joinName() default "";

    /**
     * 左連接
     *
     * @return
     */
    Join join() default Join.LEFT;

    /**
     * 多字段模糊搜索,僅支持String類型字段,多個用逗號隔開, 如用戶名和手機號模糊查詢@Query(blurry = "username,phone")
     *
     * @return
     */
    String blurry() default "";

    enum Type {
        /**
         * 相等,精確查詢
         */
        EQUAL,
        /**
         * 大於等於
         */
        GREATER_THAN,
        /**
         * 小於等於
         */
        LESS_THAN,
        /**
         * 左右均模糊查詢:%a%
         */
        INNER_LIKE,
        /**
         * 左模糊查詢:%a
         */
        LEFT_LIKE,
        /**
         * 右模糊查詢: a%
         */
        RIGHT_LIKE,
        /**
         * 小於
         */
        LESS_THAN_NQ,
        /**
         * 在...範圍內
         */
        IN,
        /**
         * 或者
         */
        OR
    }

    enum Join {
        /**
         * 左連接
         */
        LEFT,
        /**
         * 右連接
         */
        RIGHT,
    }
}
二、定義Predicate轉換類(該類可Copy直接使用)

該類內部通過反射獲取多條件查詢屬性,進行動態拼接。實現了多條件 精確查詢、模糊查詢、範圍查詢,關聯查詢。基本操作是夠用了。

@Slf4j
public class QueryUtil {
    @SuppressWarnings("unchecked")
    public static <R, Q> Predicate getPredicate(Root<R> root, Q query, CriteriaBuilder cb) {
        List<Predicate> list = new ArrayList<>();

        if (query == null) {
            return cb.and(list.toArray(new Predicate[list.size()]));
        }
        try {
            List<Field> fields = getAllFields(query.getClass(), new ArrayList<>());
            for (Field field : fields) {
                boolean accessible = field.isAccessible();
                field.setAccessible(true);
                Query q = field.getAnnotation(Query.class);
                if (q != null) {
                    String propName = q.propName();
                    String joinName = q.joinName();
                    String blurry = q.blurry();
                    String attributeName = isBlank(propName) ? field.getName() : propName;
                    Class<?> fieldType = field.getType();
                    Object val = field.get(query);
                    if (ObjectUtil.isNull(val) || "".equals(val)) {
                        continue;
                    }
                    Join join = null;
                    // 模糊多字段
                    if (ObjectUtil.isNotEmpty(blurry)) {
                        String[] blurrys = blurry.split(",");
                        List<Predicate> orPredicate = new ArrayList<>();
                        for (String s : blurrys) {
                            orPredicate.add(cb.like(root.get(s)
                                    .as(String.class), "%" + val.toString() + "%"));
                        }
                        Predicate[] p = new Predicate[orPredicate.size()];
                        list.add(cb.or(orPredicate.toArray(p)));
                        continue;
                    }
                    if (ObjectUtil.isNotEmpty(joinName)) {
                        String[] joinNames = joinName.split(">");
                        for (String name : joinNames) {
                            switch (q.join()) {
                                case LEFT:
                                    if (ObjectUtil.isNotEmpty(join)) {
                                        join = join.join(name, JoinType.LEFT);
                                    } else {
                                        join = root.join(name, JoinType.LEFT);
                                    }
                                    break;
                                case RIGHT:
                                    if (ObjectUtil.isNotEmpty(join)) {
                                        join = join.join(name, JoinType.RIGHT);
                                    } else {
                                        join = root.join(name, JoinType.RIGHT);
                                    }
                                    break;
                            }
                        }
                    }
                    switch (q.type()) {
                        case EQUAL:
                            list.add(cb.equal(getExpression(attributeName, join, root)
                                    .as((Class<? extends Comparable>) fieldType), val));
                            break;
                        case GREATER_THAN:
                            list.add(cb.greaterThanOrEqualTo(getExpression(attributeName, join, root)
                                    .as((Class<? extends Comparable>) fieldType), (Comparable) val));
                            break;
                        case LESS_THAN:
                            list.add(cb.lessThanOrEqualTo(getExpression(attributeName, join, root)
                                    .as((Class<? extends Comparable>) fieldType), (Comparable) val));
                            break;
                        case LESS_THAN_NQ:
                            list.add(cb.lessThan(getExpression(attributeName, join, root)
                                    .as((Class<? extends Comparable>) fieldType), (Comparable) val));
                            break;
                        case INNER_LIKE:
                            list.add(cb.like(getExpression(attributeName, join, root)
                                    .as(String.class), "%" + val.toString() + "%"));
                            break;
                        case LEFT_LIKE:
                            list.add(cb.like(getExpression(attributeName, join, root)
                                    .as(String.class), "%" + val.toString()));
                            break;
                        case RIGHT_LIKE:
                            list.add(cb.like(getExpression(attributeName, join, root)
                                    .as(String.class), val.toString() + "%"));
                        case IN:
                            if (CollUtil.isNotEmpty((Collection<Long>) val)) {
                                list.add(getExpression(attributeName, join, root).in((Collection<Long>) val));
                            }
                            break;
                        case OR:
                            if (Boolean.valueOf(q.orPropVal())) {
                                Predicate[] pArrays = (Predicate[]) Arrays.asList(q.orPropNames()).stream().map(
                                        prop -> cb
                                                .equal(getExpression(prop, null, root).as(String.class),
                                                        q.orPropVal())).toArray();
                                cb.or(pArrays);
                            }
                            break;
                    }
                }
                field.setAccessible(accessible);
            }
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
        return cb.and(list.toArray(new Predicate[list.size()]));
    }

    @SuppressWarnings("unchecked")
    private static <T, R> Expression<T> getExpression(String attributeName, Join join, Root<R> root) {
        if (ObjectUtil.isNotEmpty(join)) {
            return join.get(attributeName);
        } else {
            return root.get(attributeName);
        }
    }

    @SuppressWarnings("unchecked")
    public static boolean isBlank(final CharSequence cs) {
        int strLen;
        if (cs == null || (strLen = cs.length()) == 0) {
            return true;
        }
        for (int i = 0; i < strLen; i++) {
            if (Character.isWhitespace(cs.charAt(i)) == false) {
                return false;
            }
        }
        return true;
    }

    @SuppressWarnings("unchecked")
    private static List<Field> getAllFields(Class clazz, List<Field> fields) {
        if (clazz != null) {
            fields.addAll(Arrays.asList(clazz.getDeclaredFields()));
            getAllFields(clazz.getSuperclass(), fields);
        }
        return fields;
    }
}
三、自定義條件查詢類UserQueryCriteria

需要什麼查詢條件在這裏面加。

注意:propName是你實體類中對應的屬性名,需和實體類屬性一致。 如果propName省略不寫,默認按照該查詢類中你定義的屬性名去查詢。如果你既沒定義propName,同時屬性名又寫錯,就會出錯了。

@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UserQueryCriteria {
    // 精確查詢
    @Query(propName = "id",type = Query.Type.EQUAL)//propName的值和實體類中屬性值一致
    private Long id;

    // 左右模糊查詢:%username%
    @Query(propName = "username",type = Query.Type.INNER_LIKE)//propName的值和實體類中屬性值一致
    private String username;//實體類中用戶名就叫做username

    // 精確查詢
    @Query(type = Query.Type.EQUAL)
    private String email;

    // 關聯查詢 joinName是外鍵名 propName是屬性名(必須與實體類中屬性名對應)type是查詢方式,此處是關聯單屬性查詢
//    @Query(joinName = "", propName = "",type = Query.Type.EQUAL)

    // 範圍查詢,此處是關聯單屬性範圍查詢。如:查詢id 在(1,2,3)中的對象
//    @Query(joinName = "", propName = "", type = Query.Type.IN)

    // 日期範圍查詢定義 type  GREATER_THAN  LESS_THAN就好

}
四、 UserService接口

可定義多條件分頁查詢,傳入查詢對象和分頁Pageable對象。
在這裏插入圖片描述

五、 UserServiceImpl中方法實現
 @Override
    public Map<String, Object> queryAll(UserQueryCriteria criteria, Pageable pageable) {
        Page<User> page = userRepository.findAll((root, criteriaQuery, criteriaBuilder) -> QueryUtil
                .getPredicate(root, criteria, criteriaBuilder), pageable);
        Map map = PageUtil.toPage(page);
        return map;
    }

PageUtil

public class PageUtil extends cn.hutool.core.util.PageUtil {

    /**
     * List 分頁
     *
     * @param page
     * @param size
     * @param list
     * @return
     */
    public static List toPage(int page, int size, List list) {
        int fromIndex = page * size;
        int toIndex = page * size + size;

        if (fromIndex > list.size()) {
            return new ArrayList();
        } else if (toIndex >= list.size()) {
            return list.subList(fromIndex, list.size());
        } else {
            return list.subList(fromIndex, toIndex);
        }
    }
    
    /**
     * list轉page
     *
     * @param list
     * @param pageable
     * @param <T>
     * @return
     */
    public static  <T> Page<T> listConvertToPage(List<T> list, Pageable pageable) {
        int start = (int) pageable.getOffset();
        int end = (start + pageable.getPageSize()) > list.size() ? list.size() : (start + pageable.getPageSize());
        return new PageImpl<T>(list.subList(start, end), pageable, list.size());
    }

    /**
     * Page 數據處理,預防redis反序列化報錯
     *
     * @param page
     * @return
     */
    public static Map toPage(Page page) {
        Map<String, Object> map = new LinkedHashMap<>(2);
        map.put("content", page.getContent());
        map.put("totalElements", page.getTotalElements());
        return map;
    }

    /**
     * @param object
     * @param totalElements
     * @return
     */
    public static Map toPage(Object object, Object totalElements) {
        Map<String, Object> map = new LinkedHashMap<>(2);
        map.put("content", object);
        map.put("totalElements", totalElements);

        return map;
    }
}
六、Controller中定義

查詢時根據需求傳入查詢條件和分頁信息。
在這裏插入圖片描述
到此就可以了。只需要在查詢類中屬性上方添加對應的查詢註解,傳入接口就好了。不用再寫好長好長的代碼,減少冗餘。是不是happy多了。

“打烊了,打烊了!”
“客官慢走,別忘了把wshanshi帶走哈!”

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章