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
在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的寫法,從而拼接到已有的代碼中,就是個問題,所以我繼續探究。數據庫
在Java後端的綜合查詢中,咱們必定會常常與JPA打交道,好比Spring Data JPA。express
在以前學習的時候,在教程的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);
因爲已經學過.equal方法,所以很容易產生慣性思惟:.equal方法是兩個參數,方法的做用是比較參數一和參數二。
那麼我猜:.in方法應該也是傳入兩個參數,一個Expression表達式和一個List數組,只要表達式的對象屬於後面的數組,就把這條記錄查詢出來。
事實上我猜錯了:.in方法只有一個參數
<T> javax.persistence.criteria.CriteriaBuilder.In<T> in(javax.persistence.criteria.Expression<? extends T> expression);
所以我陷入了懵B,這和劇本不太同樣啊,一個參數怎麼比較呢?
最後在老師的指點下,終於找到了答案。
先回來看原生查詢中的用法:
對比JPA中的用法
因此理解用法的關鍵在於明白這一點: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的寫法不一樣。
本文做者: 河北工業大學夢雲智開發團隊 - 劉宇軒 新人經驗不足,有建議歡迎交流,有意見歡迎輕噴