Spring中CriteriaBuilder.In的使用

.in方法的用途

Criteria意爲「標準、準則」,在數據庫中翻譯爲「查詢條件」,因此CriteriaBuider就是Java提供的、用來生成查詢條件的「標準生成器」。java

Criteria的in方法對應SOL語句中的IN關鍵字。
好比,git

// 在student表中查詢全部班級id爲1或2的學生
SELECT * FROM `student` WHERE klass_id IN (1, 2);

所以in的做用就是:判斷一條記錄的某個值,是否在給定的數組裏面。
上面的代碼中,給定了1,2兩個班級,那麼在全部學生中,只要學生所在的班級是1或2中的任何一個,這個學生都會被查出來。github

.in的用法

baeldung中有比較詳細的教程,但原文仍是不夠通俗。
接下來我把文章翻譯爲中文,把代碼處理的更易懂一些。spring

in( )方法接受一個Expression並返回CriteriaBuilder.In類型的新 Predicate。 它可用於測試給定表達式是否包含在值列表中。
/**
 * 單詞解釋:
 * criteria: 標準
 * criteriaQuary: 標準查詢
 * criteriaBuider: 標準生成器
 * clause: 規則、法則
 *
 * @Input 一個課程數組klasses
 */
 
// 構建一個標準查詢,這個criteriaQuery是查詢主體,用它來發起「查詢」這個動做
CriteriaQuery<Student> criteriaQuery = 
  criteriaBuilder.createQuery(Student.class);
  
// root是須要查詢的表,代碼中的root基於Student,因此指的是「學生」表
Root<Student> root = criteriaQuery.from(Student.class);

// 使用buider構建一個in的查詢規則,in()的參數是被查詢的對象中的某個屬性(好比學生的班級)
// 稍後做比較時,就用這個屬性(學生的班級)和數組(傳入的一組班級)作對比
In<Klass> inClause = criteriaBuilder.in(root.get("klass"));

// klasses是傳入的數組,經過循環,把數組中的每一個值依次添加到in的查詢規則中
// 好比,klasses有1,2兩個班,那麼in查詢規則就包含這兩個班
for (String klass : klasses) {
    inClause.value(klass);
}

// 經過「標準查詢」,對學生表,發起「查詢」操做,查詢條件爲in查詢規則
criteriaQuery.select(root).where(inClause);

到此就明白了in的基本用法,然而,項目使用的是JPA,並且已經有了其餘查詢條件,如何把這種原生查詢的寫法,改寫成適用於JPA的寫法,從而拼接到已有的代碼中,就是個問題,所以我繼續探究。數據庫

Spring Data JPA中的specification

在Java後端的綜合查詢中,咱們必定會常常與JPA打交道,好比Spring Data JPAexpress

在以前學習的時候,在教程的4.6.3 綜合查詢中就學習瞭如何在Repository中進行條件查詢。segmentfault

specification譯爲規範、說明書。
在Repository中,咱們只須要寫好Specification,而後傳到倉庫的方法中,倉庫就會按照傳入的的查詢條件進行查詢。後端

例如,先用CriteriaBuider寫一個查詢條件:api

/**
 * 學生查詢條件
 */
public class StudentSpecs {
    /**
     * 屬於某個班級
     * @param klass 班級
     */
    public static Specification<Student> belongToKlass(Klass klass) {
        return (Specification<Student>) (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.equal(root.get("klass").as(Klass.class), klass);
    }
}

而後在倉庫層findAll方法中加入這個查詢條件:數組

default Page findAll(Klass klass, @NotNull Pageable pageable) {
        Specification<Student> specification = StudentSpecs.belongToKlass(klass);
        return this.findAll(specification, pageable);
    }

這種方式與上面講到的原生查詢方式相比,優點在於剝離了查詢的「方法」和其中的查詢「條件」:

  • 定義查詢條件時,只須要單獨維護、測試這一個條件便可
  • 實際查詢時,能夠把現有的查詢條件靈活組合

區別

經過對比,原生的查詢方法,是由標準查詢criteriaQuery發起的查詢操做。
而在JPA中,咱們經過標準構造器criteriaBuilder生成查詢條件Specification,而後傳給倉庫便可,這是寫法上最明顯的區別。

// 生成查詢條件的語句
return (Specification<Student>) (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.equal(root.get("klass").as(Klass.class), klass);

在上述代碼中,返回值類型是Specification查詢條件,這裏至關於寫了一個匿名函數,傳入了原生查詢中的三個重要參數:查詢表名root、標準查詢criteriaQuery、標準構造器criteriaBuilder。
匿名函數調用了.equal方法,用來判斷第一個參數第二個參數是否相等,方法介紹以下:

javax.persistence.criteria.Predicate equal(javax.persistence.criteria.Expression<?> expression, javax.persistence.criteria.Expression<?> expression1);

in方法在Spring Data JPA中的特殊用法

探究過程(可略過)

因爲已經學過.equal方法,所以很容易產生慣性思惟:.equal方法是兩個參數,方法的做用是比較參數一和參數二。
那麼我猜:.in方法應該也是傳入兩個參數,一個Expression表達式和一個List數組,只要表達式的對象屬於後面的數組,就把這條記錄查詢出來。

事實上我猜錯了:.in方法只有一個參數

<T> javax.persistence.criteria.CriteriaBuilder.In<T> in(javax.persistence.criteria.Expression<? extends T> expression);

所以我陷入了懵B,這和劇本不太同樣啊,一個參數怎麼比較呢?

最後在老師的指點下,終於找到了答案。

用法

先回來看原生查詢中的用法:

  1. 新建標準查詢query
  2. 用標準查詢建立root
  3. 用buider構建查詢規則clause
  4. 用query調用clause完成查詢

對比JPA中的用法

  1. 新建查詢條件Specification
  2. 執行匿名函數,傳入原生查詢中的三個參數
  3. 直接返回用buider構建的查詢條件

因此理解用法的關鍵在於明白這一點:JPA查詢中返回的內容,實質上就是原生查詢中的clause!!

接下來再把.equal和.in對比:

// 生成.equal查詢條件的語句
return (Specification<Student>) (root, criteriaQuery, criteriaBuilder) -> criteriaBuilder.equal(root.get("klass").as(Klass.class), klass);

.equal在項目中是直接return了一個匿名函數,若是想把.in也寫成這種寫法,如何操做呢?

由於.in須要手動向數組添加值,所以沒法一步完成,因此首先把匿名箭頭函數變成花括號的形式:

// 生成.equal查詢條件的語句
return (Specification<Student>) (root, criteriaQuery, criteriaBuilder) -> {
    // todo 方法內容
};

接下來原封不動的把Bealdung的示例代碼複製進去:

// 生成.equal查詢條件的語句
return (Specification<Student>) (root, criteriaQuery, criteriaBuilder) -> {
    // 構建一個標準查詢,這個criteriaQuery是查詢主體,用它來發起「查詢」這個動做
    CriteriaQuery<Student> criteriaQuery = 
      criteriaBuilder.createQuery(Student.class);

    // root是須要查詢的表,代碼中的root基於Student,因此指的是「學生」表
    Root<Student> root = criteriaQuery.from(Student.class);

    // 使用buider構建一個in的查詢規則,in()的參數是被查詢的對象中的某個屬性(好比學生的班級)
    // 稍後做比較時,就用這個屬性(學生的班級)和數組(傳入的一組班級)作對比
    In<Klass> inClause = criteriaBuilder.in(root.get("klass"));

    // klasses是傳入的數組,經過循環,把數組中的每一個值依次添加到in的查詢規則中
    // 好比,klasses有1,2兩個班,那麼in查詢規則就包含這兩個班
    for (String klass : klasses) {
        inClause.value(klass);
    }

    // 經過「標準查詢」,對學生表,發起「查詢」操做,查詢條件爲in查詢規則
    criteriaQuery.select(root).where(inClause);
};

這時候必然會報錯(返回值類型錯誤),而後再運用剛纔的理論:再也不使用query完成查詢,而是直接把clause返回,從而能夠傳遞給倉庫來處理:

// 生成.equal查詢條件的語句
return (Specification<Student>) (root, criteriaQuery, criteriaBuilder) {
    // 構建一個標準查詢,這個criteriaQuery是查詢主體,用它來發起「查詢」這個動做
    CriteriaQuery<Student> criteriaQuery = 
      criteriaBuilder.createQuery(Student.class);

    // root是須要查詢的表,代碼中的root基於Student,因此指的是「學生」表
    Root<Student> root = criteriaQuery.from(Student.class);

    // 使用buider構建一個in的查詢規則,in()的參數是被查詢的對象中的某個屬性(好比學生的班級)
    // 稍後做比較時,就用這個屬性(學生的班級)和數組(傳入的一組班級)作對比
    In<Klass> inClause = criteriaBuilder.in(root.get("klass"));

    // klasses是傳入的數組,經過循環,把數組中的每一個值依次添加到in的查詢規則中
    // 好比,klasses有1,2兩個班,那麼in查詢規則就包含這兩個班
    for (String klass : klasses) {
        inClause.value(klass);
    }

    // 直接返回查詢規則便可
    return inClause;
};

報錯消失,爲何呢?由於JPA的Specification和criteriaBuilder生成的Clause本質上就是一個東西,因此返回值類型是兼容的。

到此就解決了SpringDataJPA使用criteriaQuery.in的寫法不一樣。

版權聲明

本文做者: 河北工業大學夢雲智開發團隊 - 劉宇軒 新人經驗不足,有建議歡迎交流,有意見歡迎輕噴
相關文章
相關標籤/搜索