Spring Data JPA:解析CriteriaBuilder

時空穿越者 發表於 2021-08-29
Spring

原始碼

在Spring Data JPA相關的文章[地址]中提到了有哪幾種方式可以構建Specification的例項,該處需要藉助CriteriaBuilder,回顧一下Specification中toPredicate方法的定義,程式碼如下:

    /**
     * Creates a WHERE clause for a query of the referenced entity in form of a {@link Predicate} for the given
     * {@link Root} and {@link CriteriaQuery}.
     *
     * @param root must not be {@literal null}.
     * @param query must not be {@literal null}.
     * @param criteriaBuilder must not be {@literal null}.
     * @return a {@link Predicate}, may be {@literal null}.
     */
    @Nullable
    Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder);

 

CriteriaBuilder介面定義在包路徑javax.persistence.criteria下,程式碼如下:

/**
 * Used to construct criteria queries, compound selections, 
 * expressions, predicates, orderings.
 *
 * <p> Note that <code>Predicate</code> is used instead of <code>Expression&#060;Boolean&#062;</code> 
 * in this API in order to work around the fact that Java 
 * generics are not compatible with varags.
 *
 * @since 2.0
 */
public interface CriteriaBuilder {

 

CriteriaBuilder中的一些方法如下圖所示:

Spring Data JPA:解析CriteriaBuilder

解讀:

(1)CriteriaBuilder中的方法分為幾個類別,譬如:ordering、aggregate functions、subqueries、equality、comparisons等等。

(2)CriteriaBuilder中的方法的返回值主要有CriteriaQuery、Expression、Predicate等幾種型別。

 

示例

觀察CriteriaBuilder中and方法與or方法的定義,如下:

    /**
     * Create a conjunction of the given boolean expressions.
     * @param x  boolean expression
     * @param y  boolean expression
     * @return and predicate
     */
    Predicate and(Expression<Boolean> x, Expression<Boolean> y);
    
    /**
     * Create a conjunction of the given restriction predicates.
     * A conjunction of zero predicates is true.
     * @param restrictions  zero or more restriction predicates
     * @return and predicate
     */
    Predicate and(Predicate... restrictions);

    /**
     * Create a disjunction of the given boolean expressions.
     * @param x  boolean expression
     * @param y  boolean expression
     * @return or predicate
     */
    Predicate or(Expression<Boolean> x, Expression<Boolean> y);

    /**
     * Create a disjunction of the given restriction predicates.
     * A disjunction of zero predicates is false.
     * @param restrictions  zero or more restriction predicates
     * @return or predicate
     */
    Predicate or(Predicate... restrictions);

解讀:

上述and方法與or方法用於組合多個查詢條件。

其中Predicate and(Predicate... restrictions);方法使用不定數引數Predicate... restrictions,使用and連線詞連線起來,通常可以傳入多個Predicate引數,最佳實踐是傳入一個陣列。

 

具體案例——新增多個查詢條件

  private Specification createSpecification(Integer specialId, String specialEmail) {
    Specification<User> specification = new Specification<>() {
      @Override
      public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
        Path id = root.get("id");
        Predicate predicateId = cb.gt(id, specialId);

        Path email = root.get("email");
        Predicate predicateEmail = cb.equal(email, specialEmail);
        
        return cb.and(predicateId, predicateEmail);
      }
    };

    return specification;
  }

解讀:

上述著色處程式碼以and方法將兩個條件組合在一起


 

Predicate與Expression

從前面的分析可知,CriteriaBuilder中的很多方法接受Expression或者Predicate型別的引數,並返回Expression或者Predicate型別的結果,譬如上面提到的and方法,所以本小節來探尋一下Expression與Predicate之間的關係。

Expression與Predicate之間的關係如下圖所示:

Spring Data JPA:解析CriteriaBuilder

解讀:

Predicate介面繼承了Expression介面,所以CriteriaBuilder中接受Expression型別引數的方法(譬如:and方法等)可以接受Predicate型別的引數,正如前面示例所展示的那樣。

 

Note:

Path介面也繼承了Expression介面,所以CriteriaBuilder中接受Expression型別引數的方法(譬如:lt方法)可以接受Path型別例項:

    Specification<User> specification = new Specification<>() {
      @Override
      public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
        Path<Integer> path = root.get("id");
        return cb.lt(path, id);
      }
    };

解讀:

上述示例在構造了Path型別的變數後呼叫了lt方法,該方法的定義如下:

    /**
     * Create a predicate for testing whether the first argument is 
     * less than the second.
     * @param x  expression
     * @param y  value
     * @return less-than predicate
     */
    Predicate lt(Expression<? extends Number> x, Number y);