• <noscript id="e0iig"><kbd id="e0iig"></kbd></noscript>
  • <td id="e0iig"></td>
  • <option id="e0iig"></option>
  • <noscript id="e0iig"><source id="e0iig"></source></noscript>
  • SpringBoot中基于JPA的多條件動態查詢封裝

    標簽: SpringDataJPA

    最近地攤經濟盛行了,對于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帶走哈!”

    版權聲明:本文為weixin_43770545原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接和本聲明。
    本文鏈接:https://blog.csdn.net/weixin_43770545/article/details/106664743

    智能推薦

    基于MybatisPlus實現多表的關聯查詢,實現分頁,多條件查詢

    1、緣由 實現商品表,通過表內的id關聯查詢到供應商表的name屬性 商品表 如下所示 供應商表 如下所示 新建查詢測試sql 注意, 1、查詢tb_goods表的全部,所以用t1.*,查詢t2表的name,并用supplierName替換名稱 2、使用LEFT JOIN鏈入tb_supplier表,tb_goods為主表 3、使用on來判斷連接條件 4、where下面是查詢條件,用like模糊查...

    vxe-table封裝多條件查詢組件

    1、multiQuery.vue 引用了queryButton 組件 2、效果圖...

    多條件查詢的低級錯誤

    多條件查詢的低級錯誤 我覺得大家在多條件查詢時使用的都是搜索按鈕,也就是button標簽裝的按鈕。 而有時候在寫完查詢方法后,在操作時,點擊按鈕卻沒反應,或者說直接把整個頁面刷新了,而你的代碼又沒問題的時候,那么這個問題就是:你的button標簽里面少了個 type=“button” 正確標準的搜索按鈕的標簽是這樣滴: 下面就說說,多條件查詢的方法:最簡單的一種 就是單單一...

    多條件模糊查詢的實現

    多條件模糊查詢功能是所有信息系統的標配功能,其核心原理在于如何有效的建立模糊查詢的SQL語句。這里把以前經手的項目中的局部代碼整理并分享出來,供大家參考復用,歡迎Copy&Paste。 簡要說明 接下來展示的Demo有5個數據字段作為模糊查詢的條件,任意輸入就可以進行多條件組合模糊查詢。 代碼部分 以下代碼在Delphi7下正常運行。需要注意2點: 當全部查詢條件為空值時,要對sql語句進...

    mybatis 動態sql語句實現多條件查詢(foreach的使用)

    一、前言 現有一個需求:實現多條件、不確定條件的搜索功能。 類似于淘寶網進行搜索商品的時候,可以在搜索框進行模糊搜索,同時可以進行條件篩選,例如想買一只 口紅? 的時候,可以在搜索框內輸入“口紅”,還可以選擇品牌、是否包郵、價格區間等等。。最后搜索出來的結果是滿足所有篩選的條件的。 這里我認為的難點就是:這些條件你不確定需不需要,你不確定選了幾個,所以說這些都是動態的。 我...

    猜你喜歡

    Unity_Shader高級篇_13.1_Unity Shader入門精要

    13.4 再談邊緣檢測 在12.3中,我們曾使用Sobel算子對屏幕圖像進行邊緣測試,實現描邊的效果。但是,這種直接利用顏色信息進行邊緣檢測的方法會產生很對我們不希望得到的邊緣線,如圖13.8所示。 可以看出,物體的紋理、陰影等位置也被描上黑邊,而這往往不是我們希望看到的。在本節中,我們將學習如何在深度和法線上進行邊緣檢測,這些圖像不會受紋理和光照的影響,而僅僅保存了當前渲染物體的模型信息,通過這...

    Seata AT 模式 原理詳解

    目錄 前提 整體機制 寫隔離 讀隔離 工作機制 一階段 二階段-回滾 二階段-提交 附錄 回滾日志表 前提 基于支持本地 ACID 事務的關系型數據庫。 Java 應用,通過 JDBC 訪問數據庫。 整體機制 兩階段提交協議的演變: 一階段:業務數據和回滾日志記錄在同一個本地事務中提交,釋放本地鎖和連接資源。 二階段: 提交異步化,非常快速地完成。 回滾通過一階段的回滾日志進行反向補償。 寫隔離 ...

    Python爬蟲 | 滑動驗證碼**

    極驗驗證碼:需要手動拼合滑塊來完成的驗證,相對圖形驗證碼識別難度上升了幾個等級。下面用程序識別并通過極驗驗證碼的驗證,其中有分析識別思路、識別缺口位置、生成滑塊拖動、模擬實現滑塊拼合通過驗證等步驟。需要用到Chrome 瀏覽器,并配置 ChromeDriver ,要用到的 Python 庫是 Selenium。 1、 對極驗驗證碼了解   極驗驗證碼官網:http://www.geetest.co...

    MobaXterm root用戶連接虛擬機時出現Access denied

    1.linux打開ssh服務 2.新建連接 首先在romote host中填入要連接的主機ip specify username中填入連接的用戶名 port為連接端口默認為22 輸入連接用戶的密碼 linux默認不顯示密碼 發現密碼正確但是連接不上 問題解決 /etc/ssh/sshd_config 配置問題: #PermitRootLogin prohibit-password將該行改為Perm...

    Linux C 預處理命令

    預處理命令 一、宏定義 C語言標準允許在程序中用一個標識符來表示一個字符串,成為宏。標識符為宏名 ,在編譯預處理時,將程序中所有的宏名用相應的字符串來替換,這個過程稱為宏替換,宏分為兩種:無參數的宏和有參數的宏。 1.無參數的宏 無參數宏定義的一般形式為:#define 標識符字符串 “#”代表本行是編譯預處理命令。define是宏定義的關鍵詞,標識符是宏名。字符串是宏名所...

    精品国产乱码久久久久久蜜桃不卡