在優銳課的學習分享中探討了關於,Spring Data JPA的創建主要是為了通過按方法名稱生成查詢來輕松創建查詢。 但是,有時我們需要創建復雜的查詢,而無法利用查詢生成器。碼了很多知識筆記分享給大家。
Spring Data JPA提供了一個存儲庫編程模型,該模型以每個受管域對象的接口開頭。 定義這些接口有兩個目的:首先,通過擴展JpaRepository,我們獲得了一堆通用的CRUD方法,例如save,findAll,delete等。 其次,這將允許Spring Data JPA存儲庫基礎結構掃描該接口的類路徑並為其創建Spring Bean。 典型的存儲庫界面如下所示:
1 public interface CustomerRepository extends JpaRepository<Customer, Long> { 2 3 Customer findByEmailAddress(String emailAddress); 4 5 List<Customer> findByLastname(String lastname, Sort sort); 6 7 Page<Customer> findByFirstname(String firstname, Pageable pageable); 8 9 } 10 11
要創建復雜的查詢,為什么要指定規格?
是的,可以使用Criteria API構建復雜的查詢。 要了解為什么要使用規范,我們考慮一個簡單的業務需求。 我們將使用Criteria API以及隨后的規范來實現此要求。
這是用例:在客戶生日那天,我們希望向所有長期客戶發送優惠券。 我們如何檢索一個匹配的?
我們有兩個謂詞:
1 LocalDate today = new LocalDate(); 2 3 CriteriaBuilder builder = em.getCriteriaBuilder(); 4 5 CriteriaQuery<Customer> query = builder.createQuery(Customer.class); 6 7 Root<Customer> root = query.from(Customer.class); 8 9 Predicate hasBirthday = builder.equal(root.get(Customer_.birthday), today); 10 11 Predicate isLongTermCustomer = builder.lessThan(root.get(Customer_.createdAt), today.minusYears(2); 12 13 query.where(builder.and(hasBirthday, isLongTermCustomer)); 14 15 em.createQuery(query.select(root)).getResultList(); 16 17
在上面的代碼中,
- ·第一行創建了LocalDate以比較客戶的生日和今天的日期。
- ·以下三行包含用於設置必要的JPA基礎結構實例的樣板代碼。
- ·然后,在接下來的兩行中,我們將構建謂詞
- ·在最后兩行中,一個用於連接兩個謂詞,最后一個用於執行查詢。
此代碼的主要問題在於謂詞不易於外部化和重用,因為您需要先設置CriteriaBuilder,CriteriaQuery和Root。 另外,由於難以快速推斷出代碼的意圖,因此代碼的可讀性很差。
規格
為了能夠定義可重用謂詞,我們引入了規范接口,該接口源自Eric Evans的《域驅動設計》一書中引入的概念。 它將規范定義為實體的謂詞,這正是規范接口所代表的含義。 實際上,這僅包含一個方法:
1 public interface Specification<T> { 2 3 Predicate toPredicate(Root<T> root, CriteriaQuery query, CriteriaBuilder cb); 4 5 } 6 7
使用Java 8時,代碼變得非常清晰易懂。
1 public CustomerSpecifications { 2 3 public static Specification<Customer> customerHasBirthday() { 4 5 return (root, query, cb) ->{ 6 7 return cb.equal(root.get(Customer_.birthday), today); 8 9 }; 10 11 } 12 13 public static Specification<Customer> isLongTermCustomer() { 14 15 return (root, query, cb) ->{ 16 17 return cb.lessThan(root.get(Customer_.createdAt), new LocalDate.minusYears(2)); 18 19 }; 20 21 } 22 23 } 24 25
客戶端現在可以執行以下操作:
1 customerRepository.findAll(hasBirthday()); 2 3 customerRepository.findAll(isLongTermCustomer()); 4 5
在這里,基本實現將為您准備CriteriaQuery,Root和CriteriaBuilder,應用由給定規范創建的謂詞並執行查詢。
我們只是創建了可以單獨執行的可重用謂詞。 我們可以結合使用這些單獨的謂詞來滿足我們的業務需求。 我們有一個幫助程序類規范,它提供了(和)和(或)方法來連接原子規范。
1 customerRepository.findAll(where(customerHasBirthday()).and(isLongTermCustomer()));
與僅使用JPA Criteria API相比,它讀起來很流利,提高了可讀性並提供了更多的靈活性。 這里唯一需要說明的是,提出規范實現需要相當多的編碼工作。
以下是規范的一些優點:
- 所有“基本”查詢都已實現,即findById,保存和刪除
- 分頁功能開箱即用。 您可以簡單地將一個可分頁對象從Controller傳遞到Service到您的存儲庫,並且可以正常工作(甚至可以排序)!
- 使用Spring的Specification API比普通的JPA簡單一些,因為您只需創建謂詞,而不必弄亂EntityManager和PersistenceContext。
如果您有任何要添加或共享的內容,請在下面的評論部分中留言。
祝大家學習愉快!