JPA使用Specification pattern 進行資料查詢
這篇文章介紹了在JPA中 如何使用specification pattern來查詢資料庫中所需要的資料。 主要是如何將JPA Criteria queries與specification pattern相結合來在關係型資料庫中獲取所需要的物件。
這裡主要用一個Poll類(選舉)作為一個實體類在生成specification。 這個實體類中有start date 與end date來表示選舉的開始時間以及結束時間。在這期間使用者可以發起vote, 也就是投票。 如果一輪選舉還沒有到達結束時間,但是被Anministrator主動關閉了,那麼用lock data來代表關閉的時間。
@Entity
public class Poll {
@Id
@GeneratedValue
private long id;
private DateTime startDate;
private DateTime endDate;
private DateTime lockDate;
@OneToMany(cascade = CascadeType.ALL)
private List<Vote> votes = new ArrayList<>();
}
為了更好的可讀性,在這裡省略了各種setter以及getter方法
現在我們假設有兩個約束需要實現來查詢我們的資料庫
- poll 這輪選舉正在進行中 條件:沒有主動被關閉同時 startdate<current time<enddate
- poll 是非常popular的 條件:沒有主動被關閉 同時其中的投票超過了100
通常一般情況下 我們有兩種方法, 要麼寫一個 poll.isCurrentlyRunning()方法或者使用service例如pollService.isCurrentlyRunning(poll). 但是這兩個方法都是判斷一個poll是否正在進行,如果我們的需求是在資料庫中查詢所有正在進行的poll,那麼我們可能需要使用JPA提供的repository方法:pollRepository.findAllCurrentlyRunningPolls().
下面介紹瞭如何使用JPA提供的specification pattern來進行查詢,並且同時結合以上兩種約束來找到沒有被關閉的popular的poll
首先需要一個建立一個specification 介面:
public interface Specification<T> {
boolean isSatisfiedBy(T t);
Predicate toPredicate(Root<T> root, CriteriaBuilder cb);
Class<T> getType();
}
然後寫一個抽象類來繼承這個介面,實現裡面的方法:
abstract public class AbstractSpecification<T> implements Specification<T> {
@Override
public boolean isSatisfiedBy(T t) {
throw new NotImplementedException();
}
@Override
public Predicate toPredicate(Root<T> poll, CriteriaBuilder cb) {
throw new NotImplementedException();
}
@Override
public Class<T> getType() {
ParameterizedType type = (ParameterizedType) this.getClass().getGenericSuperclass();
return (Class<T>) type.getActualTypeArguments()[0];
}
}
這裡先忽略掉getType()這個方法,之後會解釋
這裡最重要的方法就是 isSatisfiedBy(), 它主要是用來判斷我們的物件是否符合所謂的specification,toPredicate 返回一個約束作為javax.persistence.criteria.Predicate的例項,這個約束主要是用來查詢資料庫的時候用的。
對於上述
- poll 這輪選舉正在進行中 條件:沒有主動被關閉同時 startdate<current time<enddate
- poll 是非常popular的 條件:沒有主動被關閉 同時其中的投票超過了100
這兩個查詢條件,我們會生成兩個新的specification的類(繼承 AbstractSpecification<T> ),在其中具體的實現 isSatisfiedBy(T t) 和 toPredicate(Root<T> poll, CriteriaBuilder cb) 兩個方法。
**IsCurrentlyRunning ** 判斷這個poll是否當前正在進行,
public class IsCurrentlyRunning extends AbstractSpecification<Poll> {
@Override
public boolean isSatisfiedBy(Poll poll) {
return poll.getStartDate().isBeforeNow()
&& poll.getEndDate().isAfterNow()
&& poll.getLockDate() == null;
}
@Override
public Predicate toPredicate(Root<Poll> poll, CriteriaBuilder cb) {
DateTime now = new DateTime();
return cb.and(
cb.lessThan(poll.get(Poll_.startDate), now),
cb.greaterThan(poll.get(Poll_.endDate), now),
cb.isNull(poll.get(Poll_.lockDate))
);
}
}
在 isSatisfiedBy(Poll poll) 我們判斷當前傳進來的poll是否正在進行,在 toPredicate(Root<Poll> poll, CriteriaBuilder cb) 裡面,主要我們的目的是利用一個JPA's CriteriaBuilder 構造一個 Predicate 例項,之後會使用這個實力在構建一個 CriteriaQuery 來查詢資料庫。cb.and() 與 &&相同。
在建立一個specification, IsPopular 判斷這個poll是否是popular
public class IsPopular extends AbstractSpecification<Poll> {
@Override
public boolean isSatisfiedBy(Poll poll) {
return poll.getLockDate() == null && poll.getVotes().size() > 100;
}
@Override
public Predicate toPredicate(Root<Poll> poll, CriteriaBuilder cb) {
return cb.and(
cb.isNull(poll.get(Poll_.lockDate)),
cb.greaterThan(cb.size(poll.get(Poll_.votes)), 100)
);
}
}
現在如果測試給定一個poll的例項, 我們可以根據這個poll才生成這兩個約束的specification同時判斷是否滿足條件:
boolean isPopular = new IsPopular().isSatisfiedBy(poll);
boolean isCurrentlyRunning = new IsCurrentlyRunning().isSatisfiedBy(poll);
我們需要擴充倉庫類用來查詢資料庫。
public class PollRepository {
private EntityManager entityManager = ...
public <T> List<T> findAllBySpecification(Specification<T> specification) {
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
// use specification.getType() to create a Root<T> instance
CriteriaQuery<T> criteriaQuery = criteriaBuilder.createQuery(specification.getType());
Root<T> root = criteriaQuery.from(specification.getType());
// get predicate from specification
Predicate predicate = specification.toPredicate(root, criteriaBuilder);
// set predicate and execute query
criteriaQuery.where(predicate);
return entityManager.createQuery(criteriaQuery).getResultList();
}
}
我們使用getType來建立 CriteriaQuery<T> 跟 Root<T> 例項。getType返回一個由子類定義的AbstractSpecification <T> 例項的通用型別。對於 IsPopular 和IsCurrentlyRunning,它返回Poll類。 沒有getType(),我們將必須在我們建立的每個規範的toPredicate()中建立CriteriaQuery <T>和Root <T>例項。 所以它只是一個小的幫手,以減少規格內的重複程式碼。 如果你提出了更好的方法,請隨意將其替換為你自己的實現。
到目前為止,specification只是我們一些約束的載體,它最主要的用途還是查詢資料庫或者檢查一個物件是否滿足特定的條件。
現在如果將這兩個約束聯合在一起成為一個條件,也就是說我們需要查詢資料庫來查詢那些既滿足是isrunning有滿足popular的poll,這個時候 我們就需要 composite specifications。通過composite specifications 我們可以將不同的spefication結合在一起。
我們在建立一個新的specification類,
public class AndSpecification<T> extends AbstractSpecification<T> {
private Specification<T> first;
private Specification<T> second;
public AndSpecification(Specification<T> first, Specification<T> second) {
this.first = first;
this.second = second;
}
@Override
public boolean isSatisfiedBy(T t) {
return first.isSatisfiedBy(t) && second.isSatisfiedBy(t);
}
@Override
public Predicate toPredicate(Root<T> root, CriteriaBuilder cb) {
return cb.and(
first.toPredicate(root, cb),
second.toPredicate(root, cb)
);
}
@Override
public Class<T> getType() {
return first.getType();
}
}
AndSpecification以兩個specification做為構造器引數,在內部的 isSatisfiedBy()和 toPredicate()中,我們返回由邏輯和操作組合的兩個規範的結果。
Specification<Poll> popularAndRunning = new AndSpecification<>(new IsPopular(), new IsCurrentlyRunning());
List<Poll> polls = myRepository.findAllBySpecification(popularAndRunning);
為了提高可讀性,我們可以在specification interface中新增一個add方法:
public interface Specification<T> {
Specification<T> and(Specification<T> other);
// other methods
}
在 AbstractSpecification<T> 中:
abstract public class AbstractSpecification<T> implements Specification<T> {
@Override
public Specification<T> add(Specification<T> other) {
return new AddSpecification<>(this, other);
}
// other methods
}
現在可以使用and()方法連結多個specification
Specification<Poll> popularAndRunning = new IsPopular().and(new IsCurrentlyRunning());
boolean isPopularAndRunning = popularAndRunning.isSatisfiedBy(poll);
List<Poll> polls = myRepository.findAllBySpecification(popularAndRunning);
當需要時,可以使用其他複合材料規格(例如OrSpecification或NotSpecification)來進一步擴充套件specification。
總結:
當使用specification pattern時,我們將業務規則移到單獨的specification類中。 這些specification類別可以通過使用 composite specifications 規格輕鬆組合。 一般來說,specification 提高了可重用性和可維護性。 另外specification 可以輕鬆進行單元測試。 有關specification pattern的更多詳細資訊,英語比較好的同學可以去讀讀Eric Evans和Martin Fowler的這篇文章。
本文章的原始碼在整理過程中,稍後放出。
相關文章
- 如何使用Java Streams進行資料庫查詢?Java資料庫
- jpa 聯合查詢資料,查詢使用者資訊與部門資訊
- 規格模式(Specification Pattern)模式
- SpringBoot整合Jpa對資料進行排序、分頁、條件查詢和過濾Spring Boot排序
- 使用Spring Data JPA進行資料庫操作Spring資料庫
- Django中views資料查詢使用locals()函式進行優化DjangoView函式優化
- spring data jpa查詢Spring
- JPA 連表查詢
- java中資料庫查詢,搭配簡單的圖形介面進行查詢Java資料庫
- 如何使用Hibernate/JPA的JPQL/HQL查詢提取?
- jpa一對多查詢
- JPA時間段查詢
- Spring JPA 聯表查詢Spring
- JPA多表關聯查詢
- jpa動態查詢與多表聯合查詢
- Spring Boot中使用JPA構建動態查詢Spring Boot
- spring data JPA 模糊查詢 --- 使用 LIKE --- 的寫法Spring
- JPA之使用JPQL進行CRUD操作
- langchain_chatchat+ollama部署本地知識庫,聯網查詢以及對資料庫(Oracle)資料進行查詢LangChain資料庫Oracle
- SpringBoot Jpa多條件查詢Spring Boot
- JPA的多表複雜查詢
- Spring JPA 定義查詢方法Spring
- JPA 之 多表聯合查詢
- Spring Boot整合Spring Data JPA進行資料庫操作Spring Boot資料庫
- spring-data-jpa Specification構建動態qlSpring
- 如何使用PL/SQL進行分級查詢WPSQL
- Java根據前端返回的欄位名進行查詢資料Java前端
- day95:flask:SQLAlchemy資料庫查詢進階&關聯查詢FlaskSQL資料庫
- MySQL - 資料查詢 - 簡單查詢MySql
- 資料庫查詢第5到8行的資料資料庫
- 如何抽取Oracle資料到文字文件進行查詢NAOracle
- 使用JPA連線資料庫資料庫
- Spring Data JPA 實現聯表查詢Spring
- Spring Data Jpa 的簡單查詢多表查詢HQL,SQL ,動態查詢, QueryDsl ,自定義查詢筆記SpringSQL筆記
- 如何使用 Eloquent 在兩個日期之間進行查詢?
- 使用Spring Reactive MongoDB進行自定義更新查詢 -Yuri MednikovSpringReactMongoDB
- Java根據前端返回的欄位名進行查詢資料的方法Java前端
- 資料庫原理實驗指導(三)使用SQL語言進行簡單查詢【轉載csdn】資料庫SQL
- Java ——MongDB 插入資料、 模糊查詢、in查詢Java