Spring-Data-JPA中使用Specification實現動態查詢
最近專案技術選型db框架選擇了使用JPA,剛開始時,使用jpa進行一些單表簡單的查詢非常輕鬆,大家寫的不亦樂乎,後來在遇到多條件動態查詢的業務場景時,發現現有的JpaRepository提供的方法和自己寫@Query已經滿足了不了需求,難不成要對所有的條件和欄位進行判斷,再寫很多個dao方法?後面查到jpa提供了圍繞Specification這個類的一系列類,來用於實現動態查詢。
首先,毫無疑問需要定義一個dao介面,這個介面除了繼承JpaRepository之外,還需要繼承JpaSpecificationExecutor。
public interface FileInfoDao extends JpaRepository<FileInfo, Long>, JpaSpecificationExecutor<FileInfo>{ }
這是我用於查詢檔案資訊的dao,我們可以看到JpaSpecificationExecutor有以下這些方法:
T findOne(Specification<T> spec); List<T> findAll(Specification<T> spec); Page<T> findAll(Specification<T> spec, Pageable pageable); List<T> findAll(Specification<T> spec, Sort sort); long count(Specification<T> spec);
findOne(spec):根據條件查詢獲取一條資料;
findAll(spec) :根據條件查詢獲取全部資料;
findAll(specification, pageable) : 根據條件分頁查詢資料,pageable設定頁碼、一頁資料量,同時返回的是Page類物件,可以通過getContent()方法拿到List集合資料;
findAll(specification, sort) : 根據條件查詢並返回排序後的資料;
count(spec) : 獲取滿足當前條件查詢的資料總數;
現在光定義了dao沒用,人家就是需要你往方法裡傳Specification的物件,Specification是一個介面,肯定沒法直接獲取物件。
public interface Specification<T> {
Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb);
}
所以我自定義了一個類實現類這個介面。
public class SinoSpecification<T> implements Specification<T> {
private TableQueryParam param;
private Class<T> clazz;
public SinoSpecification(TableQueryParam param, Class<T> clazz) {
this.param = param;
this.clazz = clazz;
}
@Override
public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
Field[] fields = clazz.getDeclaredFields();
Map<String, Object> conditions = param.getCondition();
List<Predicate> predicates = new ArrayList<>();
for (int i = 0; i < fields.length; i++) {
fields[i].setAccessible(true);
String name = fields[i].getName();
if(conditions.containsKey(name)) {
if(ObjectUtils.isEmpty(conditions.get(name))) {
continue;
}
predicates.add(cb.like(root.get(name), "%"+conditions.get(name)+"%"));
}
}
return cb.and(predicates.toArray(new Predicate[predicates.size()]));
}
}
TableQueryParam是我自定義的用於封裝查詢條件的類,這個類有一個condition屬性是條件集合。在這個類中,我是通過反射獲取實體類的欄位屬性,再去和TableQueryParam中的conditon裡的條件進行比對,如果條件中包含,則將這個條件以及查詢的值一起構造加入進Predicate,cb.like(arg1, arg2)這個方法,就代表模糊查詢,sql中的Like,arg1引數代表要查詢的欄位,arg2代表查詢的欄位的值,轉換成sql就是 arg1 LIKE arg2。當然,我這個自定義的Specification主要是用於模糊查詢的,而它不僅僅支援模糊,還可以通過CriteriaBuilder裡的方法來實現各種組合查詢,例如cb.equal()進行等於查詢,cb.greaterThan()、cb.lessThan()來實現大於小於,等等,可以說非常豐富。 定義好Specification之後,就可以呼叫前面說的findAll方法,將自定義的specification物件放進去即可。