1. 程式人生 > 程式設計 >使用Spring Data JPA的Specification構建資料庫查詢

使用Spring Data JPA的Specification構建資料庫查詢

file

Spring Data JPA最為優秀的特性就是可以通過自定義方法名稱生成查詢來輕鬆建立查詢SQL。Spring Data JPA提供了一個Repository程式設計模型,最簡單的方式就是通過擴充套件JpaRepository,我們獲得了一堆通用的CRUD方法,例如save,findAll,delete等。並且使用這些關鍵字可以構建很多的資料庫單表查詢介面:

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來完成。

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

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

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

我們有兩個謂詞查詢條件:

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

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

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。另外,程式碼的可讀性也很差。

二、specification

為了能夠定義可重用謂詞條件,我們引入了Specification介面。

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

結合Java 8的lambda表示式使用Specification介面時,程式碼變得非常簡單

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,cb) ->{ 
        return cb.lessThan(root.get(Customer_.createdAt),new LocalDate.minusYears(2));
    };
  }
}複製程式碼

現在可以通過CustomerRepository執行以下操作:

customerRepository.findAll(hasBirthday());
customerRepository.findAll(isLongTermCustomer());複製程式碼

我們建立了可以單獨執行的可重用謂詞查詢條件,我們可以結合使用這些單獨的謂詞來滿足我們的業務需求。我們可以使用 and(…)   和 or(…)連線specification。

customerRepository.findAll(where(customerHasBirthday()).and(isLongTermCustomer()));複製程式碼


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

期待您的關注