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


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

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


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

public interface CustomerRepository extends JpaRepository<customer> {
Customer findByEmailAddress(String emailAddress);
List<customer> findByLastname(String lastname, Sort sort);
Page<customer> findByFirstname(String firstname, Pageable pageable);
}
/<customer>/<customer>/<customer>
  • 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();
/<customer>/<customer>
  • 第一行LocalDate用於比較客戶的生日和今天的日期。em是javax.persistence.EntityManager
  • 下三行包含用於查詢Customer實體的JPA基礎結構實例的樣板代碼。
  • 然後,在接下來的兩行中,我們將構建謂詞查詢條件
  • 在最後兩行中,where用於連接兩個謂詞查詢條件,最後一個用於執行查詢。

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

二、specification

為了能夠定義可重用謂詞條件,我們可以使用Specification接口。

public interface Specification {
Predicate toPredicate(Root 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, query, cb) ->{
return cb.lessThan(root.get(Customer_.createdAt), new LocalDate.minusYears(2));
};
}
}
/<customer>/<customer>

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

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

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

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

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


分享到:


相關文章: