Spring-Data-JPA 動(dòng)態(tài)查詢黑科技

在開發(fā)中,用到動(dòng)態(tài)查詢的地方,所有的查詢條件包括分頁(yè)參數(shù),都會(huì)被封裝成一個(gè)查詢類XxxQuery

比如說上一篇中的Item

那么ItemQuery就像這樣

@Data
public class ItemQuery {

    private Integer itemId;//id精確查詢 =

    private String itemName;//name模糊查詢 like

    //價(jià)格查詢
    private Integer itemPrice;// 價(jià)格小于'條件' <

}

那現(xiàn)在問題來了,如何去標(biāo)識(shí)這些字段該用怎樣的查詢條件連接呢,還要考慮到每個(gè)查詢類都可以通用.


可以用字段注解,來標(biāo)識(shí)字段的查詢連接條件

//用枚舉類表示查詢連接條件
public enum MatchType {
    equal,        // filed = value
    //下面四個(gè)用于Number類型的比較
    gt,   // filed > value
    ge,   // field >= value
    lt,              // field < value
    le,      // field <= value

    notEqual,            // field != value
    like,   // field like value
    notLike,    // field not like value
    // 下面四個(gè)用于可比較類型(Comparable)的比較
    greaterThan,        // field > value
    greaterThanOrEqualTo,   // field >= value
    lessThan,               // field < value
    lessThanOrEqualTo,      // field <= value
    ;
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface QueryWord {

    // 數(shù)據(jù)庫(kù)中字段名,默認(rèn)為空字符串,則Query類中的字段要與數(shù)據(jù)庫(kù)中字段一致
    String column() default "";

    // equal, like, gt, lt...
    MatchType func() default MatchType.equal;

    // object是否可以為null
    boolean nullable() default false;

    // 字符串是否可為空
    boolean emptiable() default false;
}

好了,現(xiàn)在我們可以改造一下ItemQuery

@Data
public class ItemQuery {

    @QueryWord(column = "item_id", func = MatchType.equal)
    private Integer itemId;

    @QueryWord(func = MatchType.like)
    private String itemName;
  
    @QueryWord(func = MatchType.le)
    private Integer itemPrice;

}

現(xiàn)在,我們還需要去構(gòu)造出查詢時(shí)的動(dòng)態(tài)條件,那就創(chuàng)建一個(gè)所有查詢類的基類BaseQuery,我們把分頁(yè)的條件字段放在基類里.

/**
 * 所有查詢類的基類
 */
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public abstract class BaseQuery<T> {

    // start from 0
    protected int pageIndex = 0;
    protected int pageSize = 10;


    /**
     * 將查詢轉(zhuǎn)換成Specification
     * @return
     */
    public abstract Specification<T> toSpec();

    //JPA分頁(yè)查詢類
    public Pageable toPageable() {
        return new PageRequest(pageIndex, pageSize);
    }

    //JPA分頁(yè)查詢類,帶排序條件
    public Pageable toPageable(Sort sort) {
        return new PageRequest(pageIndex, pageSize, sort);
    }

    //動(dòng)態(tài)查詢and連接
    protected Specification<T> toSpecWithAnd() {
        return this.toSpecWithLogicType("and");
    }

    //動(dòng)態(tài)查詢or連接
    protected Specification<T> toSpecWithOr() {
        return this.toSpecWithLogicType("or");
    }

    //logicType or/and
    private Specification<T> toSpecWithLogicType(String logicType) {
        BaseQuery outerThis = this;
        return (root, criteriaQuery, cb) -> {
            Class clazz = outerThis.getClass();
            //獲取查詢類Query的所有字段,包括父類字段
            List<Field> fields = getAllFieldsWithRoot(clazz);
            List<Predicate> predicates = new ArrayList<>(fields.size());
            for (Field field : fields) {
                //獲取字段上的@QueryWord注解
                QueryWord qw = field.getAnnotation(QueryWord.class);
                if (qw == null)
                    continue;

                // 獲取字段名
                String column = qw.column();
                //如果主注解上colume為默認(rèn)值"",則以field為準(zhǔn)
                if (column.equals(""))
                    column = field.getName();

                field.setAccessible(true);

                try {

                    // nullable
                    Object value = field.get(outerThis);
                    //如果值為null,注解未標(biāo)注nullable,跳過
                    if (value == null && !qw.nullable())
                        continue;

                    // can be empty
                    if (value != null && String.class.isAssignableFrom(value.getClass())) {
                        String s = (String) value;
                        //如果值為"",且注解未標(biāo)注emptyable,跳過
                        if (s.equals("") && !qw.emptiable())
                            continue;
                    }
                    
                    //通過注解上func屬性,構(gòu)建路徑表達(dá)式
                    Path path = root.get(column);
                    switch (qw.func()) {
                        case equal:
                            predicates.add(cb.equal(path, value));
                            break;
                        case like:
                            predicates.add(cb.like(path, "%" + value + "%"));
                            break;
                        case gt:
                            predicates.add(cb.gt(path, (Number) value));
                            break;
                        case lt:
                            predicates.add(cb.lt(path, (Number) value));
                            break;
                        case ge:
                            predicates.add(cb.ge(path, (Number) value));
                            break;
                        case le:
                            predicates.add(cb.le(path, (Number) value));
                            break;
                        case notEqual:
                            predicates.add(cb.notEqual(path, value));
                            break;
                        case notLike:
                            predicates.add(cb.notLike(path, "%" + value + "%"));
                            break;
                        case greaterThan:
                            predicates.add(cb.greaterThan(path, (Comparable) value));
                            break;
                        case greaterThanOrEqualTo:
                            predicates.add(cb.greaterThanOrEqualTo(path, (Comparable) value));
                            break;
                        case lessThan:
                            predicates.add(cb.lessThan(path, (Comparable) value));
                            break;
                        case lessThanOrEqualTo:
                            predicates.add(cb.lessThanOrEqualTo(path, (Comparable) value));
                            break;
                    }
                } catch (Exception e) {
                    continue;
                }
            }
            Predicate p = null;
            if (logicType == null || logicType.equals("") || logicType.equals("and")) {
                p = cb.and(predicates.toArray(new Predicate[predicates.size()]));//and連接
            } else if (logicType.equals("or")) {
                p = cb.or(predicates.toArray(new Predicate[predicates.size()]));//or連接
            }
            return p;
        };
    }

    //獲取類clazz的所有Field,包括其父類的Field
    private List<Field> getAllFieldsWithRoot(Class<?> clazz) {
        List<Field> fieldList = new ArrayList<>();
        Field[] dFields = clazz.getDeclaredFields();//獲取本類所有字段
        if (null != dFields && dFields.length > 0)
            fieldList.addAll(Arrays.asList(dFields));

        // 若父類是Object,則直接返回當(dāng)前Field列表
        Class<?> superClass = clazz.getSuperclass();
        if (superClass == Object.class) return Arrays.asList(dFields);

        // 遞歸查詢父類的field列表
        List<Field> superFields = getAllFieldsWithRoot(superClass);

        if (null != superFields && !superFields.isEmpty()) {
            superFields.stream().
                    filter(field -> !fieldList.contains(field)).//不重復(fù)字段
                    forEach(field -> fieldList.add(field));
        }
        return fieldList;
    }
}

BaseQuery里,就通過toSpecWithAnd() toSpecWithOr()方法動(dòng)態(tài)構(gòu)建出了查詢條件.

那現(xiàn)在ItemQuery就要繼承BaseQuery,并實(shí)現(xiàn)toSpec()抽象方法

@Data
public class ItemQuery extends BaseQuery<Item> {

    @QueryWord(column = "item_id", func = MatchType.equal)
    private Integer itemId;

    @QueryWord(func = MatchType.like)
    private String itemName;
  
    @QueryWord(func = MatchType.le)
    private Integer itemPrice;

    @Override
    public Specification<Item> toSpec() {
        return super.toSpecWithAnd();//所有條件用and連接
    }
}

當(dāng)然肯定還有其他不能在BaseQuery中構(gòu)建的查詢條件,那就在子類的toSpec()實(shí)現(xiàn)中添加,

比如下面的例子,ItemQuery條件改成這樣

@QueryWord(column = "item_id", func = MatchType.equal)
private Integer itemId;

@QueryWord(func = MatchType.like)
private String itemName;

//價(jià)格范圍查詢
private Integer itemPriceMin;
private Integer itemPriceMax;

那其他條件就可以在toSpec()添加,這樣就可以很靈活的構(gòu)建查詢條件了

@Override
public Specification<Item> toSpec() {
    Specification<Item> spec = super.toSpecWithAnd();
    return ((root, criteriaQuery, criteriaBuilder) -> {
        List<Predicate> predicatesList = new ArrayList<>();
        predicatesList.add(spec.toPredicate(root, criteriaQuery, criteriaBuilder));
        if (itemPriceMin != null) {
            predicatesList.add(
                    criteriaBuilder.and(
                            criteriaBuilder.ge(
                                    root.get(Item_.itemPrice), itemPriceMin)));
        }
        if (itemPriceMax != null) {
            predicatesList.add(
                    criteriaBuilder.and(
                            criteriaBuilder.le(
                                    root.get(Item_.itemPrice), itemPriceMax)));
        }
       return criteriaBuilder.and(predicatesList.toArray(new Predicate[predicatesList.size()]));
    });
}

調(diào)用:

@Test
public void test1() throws Exception {
    ItemQuery itemQuery = new ItemQuery();
    itemQuery.setItemName("車");
    itemQuery.setItemPriceMax(50);
    itemQuery.setItemPriceMax(200);
    Pageable pageable = itemQuery.toPageable(new Sort(Sort.Direction.ASC, "itemId"));
    Page<Item> all = itemRepository.findAll(itemQuery.toSpec(), pageable);
}

現(xiàn)在這個(gè)BaseQueryQuertWord就可以在各個(gè)動(dòng)態(tài)查詢處使用了,只需在查詢字段上標(biāo)注@QueryWord注解,

然后實(shí)現(xiàn)BaseQuery中的抽象方法toSpec(),通過JpaSpecificationExecutor接口中的這幾個(gè)方法,就可以實(shí)現(xiàn)動(dòng)態(tài)查詢了,是不是很方便.

public interface JpaSpecificationExecutor<T> {
    T findOne(Specification<T> var1);
    List<T> findAll(Specification<T> var1);
    Page<T> findAll(Specification<T> var1, Pageable var2);
    List<T> findAll(Specification<T> var1, Sort var2);
    long count(Specification<T> var1);
}

原文鏈接:Spring-Data-JPA 動(dòng)態(tài)查詢黑科技 | 火堯

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末巷挥,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子掂墓,更是在濱河造成了極大的恐慌弱左,老刑警劉巖艺普,帶你破解...
    沈念sama閱讀 216,496評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件菱农,死亡現(xiàn)場(chǎng)離奇詭異缭付,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)大莫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,407評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門蛉腌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人只厘,你說我怎么就攤上這事烙丛。” “怎么了羔味?”我有些...
    開封第一講書人閱讀 162,632評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵河咽,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我赋元,道長(zhǎng)忘蟹,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,180評(píng)論 1 292
  • 正文 為了忘掉前任搁凸,我火速辦了婚禮媚值,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘护糖。我一直安慰自己褥芒,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,198評(píng)論 6 388
  • 文/花漫 我一把揭開白布嫡良。 她就那樣靜靜地躺著锰扶,像睡著了一般。 火紅的嫁衣襯著肌膚如雪寝受。 梳的紋絲不亂的頭發(fā)上坷牛,一...
    開封第一講書人閱讀 51,165評(píng)論 1 299
  • 那天,我揣著相機(jī)與錄音很澄,去河邊找鬼京闰。 笑死,一個(gè)胖子當(dāng)著我的面吹牛甩苛,可吹牛的內(nèi)容都是我干的忙干。 我是一名探鬼主播,決...
    沈念sama閱讀 40,052評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼浪藻,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼捐迫!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起爱葵,我...
    開封第一講書人閱讀 38,910評(píng)論 0 274
  • 序言:老撾萬榮一對(duì)情侶失蹤施戴,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后萌丈,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體赞哗,經(jīng)...
    沈念sama閱讀 45,324評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,542評(píng)論 2 332
  • 正文 我和宋清朗相戀三年辆雾,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了肪笋。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,711評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖藤乙,靈堂內(nèi)的尸體忽然破棺而出猜揪,到底是詐尸還是另有隱情,我是刑警寧澤坛梁,帶...
    沈念sama閱讀 35,424評(píng)論 5 343
  • 正文 年R本政府宣布而姐,位于F島的核電站,受9級(jí)特大地震影響划咐,放射性物質(zhì)發(fā)生泄漏拴念。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,017評(píng)論 3 326
  • 文/蒙蒙 一褐缠、第九天 我趴在偏房一處隱蔽的房頂上張望政鼠。 院中可真熱鬧,春花似錦队魏、人聲如沸公般。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,668評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)俐载。三九已至,卻和暖如春登失,著一層夾襖步出監(jiān)牢的瞬間遏佣,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,823評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工揽浙, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留状婶,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,722評(píng)論 2 368
  • 正文 我出身青樓馅巷,卻偏偏與公主長(zhǎng)得像膛虫,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子钓猬,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,611評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,075評(píng)論 25 707
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理稍刀,服務(wù)發(fā)現(xiàn),斷路器敞曹,智...
    卡卡羅2017閱讀 134,651評(píng)論 18 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,804評(píng)論 6 342
  • 1.簡(jiǎn)單的談一下SpringMVC的工作流程账月? 流程1、用戶發(fā)送請(qǐng)求至前端控制器DispatcherServlet...
    kerb閱讀 216評(píng)論 0 0
  • 英國(guó)作家查爾斯·狄更斯所著的歷史小說《雙城記》中有一句話是這樣說的“這是最好的時(shí)代澳迫,也是最壞的時(shí)代局齿。”北京是我這...
    飛鳥52赫茲閱讀 948評(píng)論 0 1