使用Spring Data JPA的Specification構建數據庫查詢

file

Spring Data JPA最爲優秀的特性就是能夠經過自定義方法名稱生成查詢來輕鬆建立查詢SQL。Spring Data JPA提供了一個Repository編程模型,最簡單的方式就是經過擴展JpaRepository,咱們得到了一堆通用的CRUD方法,例如save,findAll,delete等。而且使用這些關鍵字能夠構建不少的數據庫單表查詢接口:java

public interface CustomerRepository extends JpaRepository<Customer, Long> {
  Customer findByEmailAddress(String emailAddress);
  List<Customer> findByLastname(String lastname, Sort sort);
  Page<Customer> findByFirstname(String firstname, Pageable pageable);
}
  • findByEmailAddress生成的SQL是根據email_address字段查詢Customer表的數據
  • findByLastname根據lastname字段查詢Customer表的數據
  • findByFirstname根據firstname字段查詢Customer表的數據

以上全部的查詢都不用咱們手寫SQL,查詢生成器自動幫咱們工做,對於開發人員來講只須要記住一些關鍵字,如:findBy、delete等等。可是,有時咱們須要建立複雜一點的查詢,就沒法利用查詢生成器。可使用本節介紹的Specification來完成。spring

筆者仍是更願意手寫SQL來完成複雜查詢,可是有的時候偶爾使用一下Specification來完成任務,也仍是深得我心。不排斥、不盲從。沒有最好的方法,只有最合適的方法!數據庫

1、使用Criteria API構建複雜的查詢

是的,除了specification,咱們還可使用Criteria API構建複雜的查詢,可是沒有specification好用。咱們來看一下需求:在客戶生日當天,咱們但願向全部長期客戶(2年以上)發送優惠券。咱們如何該檢索Customer?編程

咱們有兩個謂詞查詢條件:api

  • 生日
  • 長期客戶-2年以上的客戶。

下面是使用JPA 2.0 Criteria API的實現方式:springboot

LocalDate today = new LocalDate();

CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Customer> query = builder.createQuery(Customer.class);
Root<Customer> root = query.from(Customer.class);

Predicate hasBirthday = builder.equal(root.get(Customer_.birthday), today);
Predicate isLongTermCustomer = builder.lessThan(root.get(Customer_.createdAt), today.minusYears(2); 

query.where(builder.and(hasBirthday, isLongTermCustomer));
em.createQuery(query.select(root)).getResultList();
  • 第一行LocalDate用於比較客戶的生日和今天的日期。em是javax.persistence.EntityManager
  • 下三行包含用於查詢Customer實體的JPA基礎結構實例的樣板代碼。
  • 而後,在接下來的兩行中,咱們將構建謂詞查詢條件
  • 在最後兩行中,where用於鏈接兩個謂詞查詢條件,最後一個用於執行查詢。

此代碼的主要問題在於,謂詞查詢條件不易於重用,您須要先設置 CriteriaBuilder, CriteriaQuery,和Root。另外,代碼的可讀性也不好。less

2、specification

爲了可以定義可重用謂詞條件,咱們引入了Specification接口。學習

public interface Specification<T> {
  Predicate toPredicate(Root<T> root, CriteriaQuery query, CriteriaBuilder cb);
}

結合Java 8的lambda表達式使用Specification接口時,代碼變得很是簡單ui

public CustomerSpecifications {
   //查詢條件:生日爲今天
  public static Specification<Customer> customerHasBirthday() {
    return (root, query, cb) ->{ 
        return cb.equal(root.get(Customer_.birthday), today);
    };
  }
  //查詢條件:客戶建立日期在兩年之前
  public static Specification<Customer> isLongTermCustomer() {
    return (root, query, cb) ->{ 
        return cb.lessThan(root.get(Customer_.createdAt), new LocalDate.minusYears(2));
    };
  }
}

如今能夠經過CustomerRepository執行如下操做:code

customerRepository.findAll(hasBirthday());
customerRepository.findAll(isLongTermCustomer());

咱們建立了能夠單獨執行的可重用謂詞查詢條件,咱們能夠結合使用這些單獨的謂詞來知足咱們的業務需求。咱們可使用 and(…)   和 or(…)鏈接specification。

customerRepository.findAll(where(customerHasBirthday()).and(isLongTermCustomer()));

與使用JPA Criteria API相比,它讀起來很流利,提升了可讀性並提供了更多的靈活性。

期待您的關注

相關文章
相關標籤/搜索