Spring-Data-JPA criteria 查詢

火堯發表於2017-10-11

Spring Data JPA雖然大大的簡化了持久層的開發,但是在實際開發中,很多地方都需要高階動態查詢

Criteria API

Criteria 查詢是以元模型的概念為基礎的,元模型是為具體持久化單元的受管實體定義的,這些實體可以是實體類,嵌入類或者對映的父類。

CriteriaQuery介面:代表一個specific的頂層查詢物件,它包含著查詢的各個部分,比如:select 、from、where、group by、order by等注意:CriteriaQuery物件只對實體型別或嵌入式型別的Criteria查詢起作用

Root介面:代表Criteria查詢的根物件,Criteria查詢的查詢根定義了實體型別,能為將來導航獲得想要的結果,它與SQL查詢中的FROM子句類似

​ 1:Root例項是型別化的,且定義了查詢的FROM子句中能夠出現的型別。

​ 2:查詢根例項能通過傳入一個實體型別給 AbstractQuery.from方法獲得。

​ 3:Criteria查詢,可以有多個查詢根。

​ 4:AbstractQuery是CriteriaQuery 介面的父類,它提供得到查詢根的方法。CriteriaBuilder介面:用來構建CritiaQuery的構建器物件Predicate:一個簡單或複雜的謂詞型別,其實就相當於條件或者是條件組合

如果編譯器能夠對查詢執行語法正確性檢查,那麼對於 Java 物件而言該查詢就是型別安全的。Java™Persistence API (JPA) 的 2.0 版本引入了 Criteria API,這個 API 首次將型別安全查詢引入到 Java 應用程式中,併為在執行時動態地構造查詢提供一種機制。

JPA元模型

在JPA中,標準查詢是以元模型的概念為基礎的.元模型是為具體持久化單元的受管實體定義的.這些實體可以是實體類,嵌入類或者對映的父類.提供受管實體元資訊的類就是元模型類.

使用元模型類最大的優勢是憑藉其例項化可以在編譯時訪問實體的持久屬性.該特性使得criteria 查詢更加型別安全.

如下,Item實體類對應的元模型Item_

@Generated(value = "org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor")
@StaticMetamodel(Item.class)
public abstract class Item_ {
   public static volatile SingularAttribute<Item, Integer> itemId;
   public static volatile SingularAttribute<Item, String> itemName;
   public static volatile SingularAttribute<Item, Integer> itemStock;
   public static volatile SingularAttribute<Item, Integer> itemPrice;
}複製程式碼

這樣的元模型不用手動建立,在Maven中新增外掛,編譯之後@Entity註解的類就會自動生成對應的元模型

<!--hibernate JPA 自動生成元模型-->
<!-- 相關依賴 -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-jpamodelgen</artifactId>
            <version>5.2.10.Final</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>5.1.0.Final</version>
        </dependency>複製程式碼
<plugin>
    <artifactId>maven-compiler-plugin</artifactId>
       <configuration>
           <source>1.8</source>
             <target>1.8</target>
              <compilerArguments>
                 <processor>org.hibernate.jpamodelgen.JPAMetaModelEntityProcessor</processor>
              </compilerArguments>
         </configuration>
 </plugin>複製程式碼

方法不止一種,具體見:Chapter 2. Usage

使用criteria 查詢簡單Demo

@Service
public class ItemServiceImpl implements ItemService {

    @Resource
    private EntityManager entityManager;

    @Override
    public List<Item> findByConditions(String name, Integer price, Integer stock) {
          //建立CriteriaBuilder安全查詢工廠
        //CriteriaBuilder是一個工廠物件,安全查詢的開始.用於構建JPA安全查詢.
        CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
        //建立CriteriaQuery安全查詢主語句
        //CriteriaQuery物件必須在實體型別或嵌入式型別上的Criteria 查詢上起作用。
        CriteriaQuery<Item> query = criteriaBuilder.createQuery(Item.class);
        //Root 定義查詢的From子句中能出現的型別
        Root<Item> itemRoot = query.from(Item.class);
          //Predicate 過濾條件 構建where字句可能的各種條件
          //這裡用List存放多種查詢條件,實現動態查詢
        List<Predicate> predicatesList = new ArrayList<>();
          //name模糊查詢 ,like語句
        if (name != null) {
            predicatesList.add(
                    criteriaBuilder.and(
                            criteriaBuilder.like(
                                    itemRoot.get(Item_.itemName), "%" + name + "%")));
        }
         // itemPrice 小於等於 <= 語句
        if (price != null) {
            predicatesList.add(
                    criteriaBuilder.and(
                            criteriaBuilder.le(
                                    itemRoot.get(Item_.itemPrice), price)));
        }
        //itemStock 大於等於 >= 語句
        if (stock != null) {
            predicatesList.add(
                    criteriaBuilder.and(
                            criteriaBuilder.ge(
                                    itemRoot.get(Item_.itemStock), stock)));
        }
          //where()拼接查詢條件
        query.where(predicatesList.toArray(new Predicate[predicatesList.size()]));
        TypedQuery<Item> typedQuery = entityManager.createQuery(query);
        List<Item> resultList = typedQuery.getResultList();
        return resultList;
    }
}複製程式碼

criteriaBuilder中各方法對應的語句

equle : filed = value

gt / greaterThan : filed > value

lt / lessThan : filed < value

ge / greaterThanOrEqualTo : filed >= value

le / lessThanOrEqualTo: filed <= value

notEqule : filed != value

like : filed like value

notLike : filed not like value

如果每個動態查詢的地方都這麼寫,那就感覺太麻煩了.

那實際上,在使用Spring Data JPA的時候,只要我們的Repo層介面繼承JpaSpecificationExecutor介面就可以使用Specification進行動態查詢了,我們先看下JpaSpecificationExecutor介面:

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);
}複製程式碼

在這裡有個很重要的介面Specification

public interface Specification<T> {
    Predicate toPredicate(Root<T> var1, CriteriaQuery<?> var2, CriteriaBuilder var3);
}複製程式碼

這個介面只有一個方法,返回動態查詢的資料結構,用於構造各種動態查詢的SQL

Specification介面示例

public Page<Item> findByConditions(String name, Integer price, Integer stock, Pageable page) {
     Page<Item> page = itemRepository.findAll((root, criteriaQuery, criteriaBuilder) -> {
            List<Predicate> predicatesList = new ArrayList<>();
            //name模糊查詢 ,like語句
            if (name != null) {
                predicatesList.add(
                        criteriaBuilder.and(
                                criteriaBuilder.like(
                                        root.get(Item_.itemName), "%" + name + "%")));
            }
            // itemPrice 小於等於 <= 語句
            if (price != null) {
                predicatesList.add(
                        criteriaBuilder.and(
                                criteriaBuilder.le(
                                        root.get(Item_.itemPrice), price)));
            }
            //itemStock 大於等於 >= 語句
            if (stock != null) {
                predicatesList.add(
                        criteriaBuilder.and(
                                criteriaBuilder.ge(
                                        root.get(Item_.itemStock), stock)));
            }
            return criteriaBuilder.and(
                    predicatesList.toArray(new Predicate[predicatesList.size()]));
        }, page);
    return page;
}複製程式碼

在這裡因為findAll(Specification<T> var1, Pageable var2)方法中引數 Specification<T> 是一個匿名內部類

那這裡就可以直接用lambda表示式直接簡化程式碼.

這樣寫,就比用CriteriaBuilder安全查詢工廠簡單多了.

呼叫:

Page<Item> itemPageList = findByConditions("車", 300, null, new PageRequest(1, 10));複製程式碼

利用JPA的Specification<T>介面和元模型就實現動態查詢了.

那其實這樣每一個需要動態查詢的地方都需要寫一個這樣類似的findByConditions方法,感覺也很麻煩了.當然是越簡化越好.

下一篇將會講一個JPASpecification更方便的使用.
原文連結:Spring-Data-JPA criteria 查詢 | 火堯

相關文章