學習Spring-Data-Jpa(十三)---動態查詢接口JpaSpecificationExecutor


1、JpaSpecificationExecutor

  JPA2引入了一個criteria API,我們可以使用它以編程的形式構建查詢。通過編寫criteria,動態生成query語句。JpaSpecificationExecutor是Spring-Data-JPA為我們執行基於JPA criteria API的Specification查詢接口。想要使用該功能,我們自己的Repository接口繼承這個接口就可以了。該接口提供了幾個根據Specification進行查詢的方法。

  JpaSpecificationExecutor源碼:

import java.util.List;
import java.util.Optional;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.lang.Nullable;

/**
 *  接口,允許執行基於JPA criteria API的Specification查詢。
 */
public interface JpaSpecificationExecutor<T> {

    /**
     * 返回匹配給定Specification的單個實體,如果沒找到返回 Optional.empty(),如果結果集有多個,拋出IncorrectResultSizeDataAccessException異常
     */
    Optional<T> findOne(@Nullable Specification<T> spec);

    /**
     * 返回匹配給定Specification的所有實體
     */
    List<T> findAll(@Nullable Specification<T> spec);

    /**
     * 返回所有匹配給定Specification的實體並分頁
     */
    Page<T> findAll(@Nullable Specification<T> spec, Pageable pageable);

    /**
     * 返回所有匹配Specification的實體並排序
     */
    List<T> findAll(@Nullable Specification<T> spec, Sort sort);

    /**
     * 返回所有匹配給定Specification的記錄數
     */
    long count(@Nullable Specification<T> spec);
}

2、Specification

      JpaSpecificationExecutor的每一個方法中都有一個Specification參數,Specification接口中的toPredicate方法是該接口的核心方法。Specification可以很容易的在實體上構建一組可擴展的Predicate。然后可以對其進行組合使用,這樣JpaRepository就不用為每一種情況都寫一個查詢方法了。

    toPredicate方法種有三個參數:

       Root<T>,代表查詢和操作實體的根,我們可以通過它的get方法來獲得我們操作的字段,可以寫實體屬性字符串,也可以使用@StaticMetamodel標記的類來指定(后面有示例)。 

       CriteriaQuery<?>,抽象了整個查詢語句,用來把各個段組合在一起。 

       CriteriaBuilder,用來構建CritiaQuery的構建器對象,其實就相當於條件或者是條件組合,以謂語即Predicate的形式返回。

  Specification源碼:

import static org.springframework.data.jpa.domain.SpecificationComposition.*;

import java.io.Serializable;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import org.springframework.lang.Nullable;

/**
 * 領域驅動設計意義上的規范。
 */
public interface Specification<T> extends Serializable {

    long serialVersionUID = 1L;

    /**
     * 否定給定的Specification
     */
    static <T> Specification<T> not(@Nullable Specification<T> spec) {

        return spec == null //
                ? (root, query, builder) -> null//
                : (root, query, builder) -> builder.not(spec.toPredicate(root, query, builder));
    }

    /**
     * 簡單的靜態工廠方法,給Specification周圍添加一些語法糖
     */
    @Nullable
    static <T> Specification<T> where(@Nullable Specification<T> spec) {
        return spec == null ? (root, query, builder) -> null : spec;
    }

    /**
     * 將給定Specification與當前Specification進行and關聯
     */
    @Nullable
    default Specification<T> and(@Nullable Specification<T> other) {
        return composed(this, other, (builder, left, rhs) -> builder.and(left, rhs));
    }

    /**
     * 將給定specification與當前specification進行or關聯
     */
    @Nullable
    default Specification<T> or(@Nullable Specification<T> other) {
        return composed(this, other, (builder, left, rhs) -> builder.or(left, rhs));
    }

    /**
     * 以給定根和CriteriaQuery的謂詞的形式為引用實體的查詢創建WHERE子句。
     */
    @Nullable
    Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder);
}

3、SpecificationComposition

  幫助類,以支持specification的組合模式。

import java.io.Serializable;

import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;

import org.springframework.data.jpa.domain.Specification;
import org.springframework.lang.Nullable;

/**
 * 幫助類,以支持specification的組合模式
 */
class SpecificationComposition {

    /**
     * 函數式接口,組合器
     */
    interface Combiner extends Serializable {
        Predicate combine(CriteriaBuilder builder, @Nullable Predicate lhs, @Nullable Predicate rhs);
    }

    /**
     *  靜態組合方法,將給定的兩個Specification用指定的組合器進行組合成新的Specification
     */
    @Nullable
    static <T> Specification<T> composed(@Nullable Specification<T> lhs, @Nullable Specification<T> rhs,
                                         Combiner combiner) {

        return (root, query, builder) -> {

            Predicate otherPredicate = toPredicate(lhs, root, query, builder);
            Predicate thisPredicate = toPredicate(rhs, root, query, builder);

            if (thisPredicate == null) {
                return otherPredicate;
            }

            return otherPredicate == null ? thisPredicate : combiner.combine(builder, thisPredicate, otherPredicate);
        };
    }

    /**
     * 將Specification轉換為Predicate對象
     */
    private static <T> Predicate toPredicate(Specification<T> specification, Root<T> root, CriteriaQuery<?> query,
            CriteriaBuilder builder) {
        return specification == null ? null : specification.toPredicate(root, query, builder);
    }
}

4、使用JPAMetaModelEntityProcessor生成元模型

  4.1、導入hibernate-jpamodelgen依賴

        <!-- 自動生成元模型 -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-jpamodelgen</artifactId>
            <version>5.2.17.Final</version>
        </dependency>

  4.2、IDEA配置Annoation Processors  

    

  4.3、和lombok一起使用,添加maven插件(如果兩個同時使用,不添加額外配置的話,lombok不生效)

            <!--     JPAMetaModelEntityProcessor與lombok共存       -->
            <plugin>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <compilerArguments>
                        <processor>org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor,lombok.launch.AnnotationProcessorHider$AnnotationProcessor</processor>
                    </compilerArguments>
                </configuration>
            </plugin>

5、JpaRepositoryImplementation

   JpaRepository和JpaSpecificationExecutor接口的子類,我們可以直接繼承改接口。

6、使用示例

  6.1、繼承接口

 

   6.2、Specification條件工廠(各條件可以組合使用)

/**
 * book Specification 條件
 *
 * @author caofanqi
 */
public class BookSpecs {


    /**
     *  書名模糊
     */
    public static Specification<Book> bookNameLike(String bookName){
        return (Specification<Book>) (root, query, builder) -> {
            //設置抓取策略,解決N+1條SQL問題
            root.fetch("category", JoinType.LEFT);
            return builder.like(root.get("bookName"),"%" + bookName + "%");
        };
    }


    /**
     * 大於前六個月的認為是新書
     */
    public static Specification<Book> isNewBook(){
        return (Specification<Book>) (root, query, builder) -> {
            LocalDate beforeSixMonth = LocalDate.now().minusMonths(6);
            root.fetch(Book_.category, JoinType.LEFT);
            return builder.greaterThan(root.get(Book_.publishDate),beforeSixMonth);
        };
    }


    /**
     *  門類名稱模糊
     */
    public static Specification<Book> categoryNameLike(String categoryName){
        return (Specification<Book>) (root, query, builder) -> {
            root.fetch(Book_.category, JoinType.INNER);
            return builder.like(root.get(Book_.category).get(Category_.categoryName),"%" + categoryName + "%");
        };
    }

}

  6.3、單元測試

    @Test
    void testSpec1(){
        List<Book> books = bookRepository.findAll(BookSpecs.bookNameLike("java"));
        books.forEach(b-> System.out.println(b.getBookName()));
    }

    @Test
    void testSpec2(){
        List<Book> books = bookRepository.findAll(BookSpecs.bookNameLike("java").and(BookSpecs.isNewBook()));
        books.forEach(b-> System.out.println(b.getBookName()));
    }

    /**
     * 排序和分頁一樣使用
     */
    @Test
    void testSpec3(){
        List<Book> books = bookRepository.findAll(BookSpecs.isNewBook(),Sort.by(Sort.Direction.DESC,"publishDate"));
        books.forEach(b-> System.out.println(b.getBookName()));
    }


    @Test
    void testSpec4(){
        List<Book> books = bookRepository.findAll(BookSpecs.categoryNameLike("數據庫"));
        books.forEach(b-> System.out.println(b.getBookName()));
    }

 

 

源碼地址:https://github.com/caofanqi/study-spring-data-jpa

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM