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, query, 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相比,它讀起來很流利,提高了可讀性並提供了更多的靈活性。
期待您的關注
- 向您推薦博主的系列文檔:《手摸手教您學習SpringBoot系列-16章97節》
- 本文轉載注明出處(必須帶連接,不能只轉文字):字母哥博客。