本次就下面這段代碼講一下對(duì)JPA模糊查詢的理解,記錄的同時(shí),希望能幫到他人。
之所以選擇解析這段代碼,是因?yàn)樽约阂惨蜻@段代碼迷惑過,并且它具有一定的代表性,管中窺豹,可見一斑,把這個(gè)例子理解了,模糊查詢也應(yīng)該了解的差不多了
//源于文件 CfgPhysicalDbServiceImpl.java
public Map<String,Object> queryAll1(CfgPhysicalDbQueryCriteria criteria, Pageable pageable){
Page<CfgPhysicalDb> page = cfgPhysicalDbRepository.findAll(
(root, criteriaQuery, criteriaBuilder) -> QueryHelp.getPredicate(root,criteria,criteriaBuilder),pageable);
return PageUtil.toPage(page.map(cfgPhysicalDbMapper::toDto));
}
一、解析該Lambda表達(dá)式
這是一個(gè)Lambda表達(dá)式,如果不明白的話可以參考文章 [https://www.cnblogs.com/CarpenterLee/p/5978721.html]
為了方便閱讀,在下面代碼中不使用lambda表達(dá)式,上述代碼等價(jià)于:
public Map<String,Object> queryAll(CfgPhysicalDbQueryCriteria criteria, Pageable pageable){
Page<CfgPhysicalDb> page = cfgPhysicalDbRepository.findAll(
new Specification<CfgPhysicalDb>() {
@Override
public Predicate toPredicate(Root<CfgPhysicalDb> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
return QueryHelp.getPredicate(root,criteria,criteriaBuilder);
}
},pageable);
return PageUtil.toPage(page.map(cfgPhysicalDbMapper::toDto));
}
二、弄清楚JPA repository 的結(jié)構(gòu)圖
可以使用 Intellij IDEA 的快捷鍵 Ctrl + Alt + Shirft + U 生成UML圖

1、我們來分析一下上圖中的幾個(gè)類:
這是業(yè)務(wù)接口 CfgPhysicalDbRepository 的代碼:
public interface CfgPhysicalDbRepository extends JpaRepository<CfgPhysicalDb, Integer>, JpaSpecificationExecutor<CfgPhysicalDb> {
}
(1)JpaRepository 接口:它的作用是定義了常用的 CRUD 方法
Repository 接口類似于 Dao 或者 mybatis 中的 mapper,含義上略有不同,我們的業(yè)務(wù)接口 CfgPhysicalDbRepository 會(huì)繼承兩個(gè)接口(接口是可以繼承多個(gè)的), JpaRepository 接口和 JpaSpecificationExecutor 接口。業(yè)務(wù)接口 CfgPhysicalDbRepository 會(huì)做兩件事,一個(gè)是在接口中定義自己的方法,如需要寫原生 sql 的方法都習(xí)慣寫在這里;第二個(gè)是繼承 JpaRepository 接口中已經(jīng)存在的 CRUD 方法。所以 JpaRepository 接口的作用是定義了常用的 CRUD 方法。
JpaRepository 接口的三個(gè)父接口中,CrudRepository 接口定義了最基本的 CRUD 方法;PagingAndSortingRepository 接口定義了分頁和排序要用到的方法; QueryByExampleExecutor 接口定義的是按實(shí)例進(jìn)行查詢的方法。
(2)JpaSpecificationExecutor 接口:自定義查詢的接口
比如模糊查詢、表關(guān)聯(lián)查詢等都通過它來完成,業(yè)務(wù)接口 CfgPhysicalDbRepository 同樣實(shí)現(xiàn)了它,我們?cè)诖a中調(diào)用的 findAll(Specification<T> spec, Pageable pageable) 方法就是由它定義的。
(3)SimpleJpaRepository 類:常用 CRUD 方法的真正實(shí)現(xiàn)類
當(dāng)業(yè)務(wù)接口 CfgPhysicalDbRepository 調(diào)用 JpaRepository 接口中的 CRUD 方法,或者 JpaSpecificationExecutor 接口中的 findAll 方法時(shí),應(yīng)該有一個(gè)業(yè)務(wù)實(shí)現(xiàn)類 CfgPhysicalDbRepositoryImpl 來實(shí)現(xiàn)這些方法,所以我們可以在同一個(gè)包下定義一個(gè)實(shí)現(xiàn)類 CfgPhysicalDbRepositoryImpl 來重寫這些方法。但是我們很少看見有人這樣寫,這樣寫就是和普通的 Dao、DaoImpl 沒有兩樣了。 JPA 有它自己的玩法,這些常用的方法, JPA 都通過接口定義好并且在 SimpleJpaRepository 中替你一一實(shí)現(xiàn)了,所以說當(dāng)代碼執(zhí)行找不到 CfgPhysicalDbRepositoryImpl 的時(shí)候,就會(huì)一直向下找,最終找到 SimpleJpaRepository 中的實(shí)現(xiàn)方法為止,此處是一個(gè)委派模式。
2、詳解 SimpleJpaRepository 的 findAll(Specification<T> spec, Pageable pageable) 方法
它的代碼如下:
public Page<T> findAll(@Nullable Specification<T> spec, Pageable pageable) {
TypedQuery<T> query = getQuery(spec, pageable);
return isUnpaged(pageable) ? new PageImpl<T>() : readPage(query, getDomainClass(), pageable, spec);
}
(1)JPA的復(fù)雜查詢都必須經(jīng)歷四個(gè)步驟:
1、獲取 builder => CriteriaBuilder builder = em.getCriteriaBuilder();
2、獲取 Query =>CriteriaQuery<Student> query = builder.createQuery(CfgPhysicalDb.class);
3、在 Query 中構(gòu)造查詢條件 => Predicate predicate = spec.toPredicate(root, query, builder);
4、執(zhí)行查詢 => query.getResultList()
這四個(gè)步驟中第1、2、4步在所有復(fù)雜查詢中都完全相同,只有第3步是不同的,所以 JPA 在 SimpleJpaRepository 將第1、2、4步都做了統(tǒng)一處理,留下第3步讓我們自己實(shí)現(xiàn),我們?cè)?CfgPhysicalDbServiceImpl.java 重寫了匿名類的 toPredicate(...) 方法就是在做第3步,上述代碼的 query.getResultList() 則完成了第4步執(zhí)行查詢。
(2)深入SimpleJpaRepository.findAll(...)中的 getQuery(Specification<T> spec, Pageable pageable)方法
protected <S extends T> TypedQuery<S> getQuery(@Nullable Specification<S> spec, Class<S> domainClass, Sort sort) {
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<S> query = builder.createQuery(domainClass);
Root<S> root = applySpecificationToCriteria(spec, domainClass, query);
query.select(root);
if (sort.isSorted()) {
query.orderBy(toOrders(sort, root, builder));
}
return applyRepositoryMethodMetadata(em.createQuery(query));
}
該方法的第一行 CriteriaBuilder builder = em.getCriteriaBuilder() 完成了復(fù)雜查詢的第1步:【獲取 builder】 ,第二行 Query CriteriaQuery<S> query = builder.createQuery(domainClass) 完成了第2步:【獲取 Query】。
我們?cè)龠M(jìn)入 applySpecificationToCriteria(spec, domainClass, query) 方法中:
private <S, U extends T> Root<U> applySpecificationToCriteria(@Nullable Specification<U> spec, Class<U> domainClass,
CriteriaQuery<S> query) {
Assert.notNull(domainClass, "Domain class must not be null!");
Assert.notNull(query, "CriteriaQuery must not be null!");
Root<U> root = query.from(domainClass);
if (spec == null) {
return root;
}
CriteriaBuilder builder = em.getCriteriaBuilder();
Predicate predicate = spec.toPredicate(root, query, builder);
if (predicate != null) {
query.where(predicate);
}
return root;
}
我們重點(diǎn)看 Predicate predicate = spec.toPredicate(root, query, builder) 這一行,此處的 toPredicate(...)是在接口 Specification 中定義的,而我們?cè)?CfgPhysicalDbServiceImpl.java 中通過匿名類的方式重寫了toPredicate(...)方法。
重寫 toPredicate(...) 的方法在網(wǎng)上一搜一大把,本例中使用 QueryHelp.getPredicate(root,criteria,criteriaBuilder),pageable) 對(duì)其進(jìn)行了封裝處理。
看到這里,一切明了,JPA 執(zhí)行復(fù)雜查詢的4個(gè)步驟都找到了各自代碼實(shí)現(xiàn)的位置,其中有三步 JPA 都替我們完成了,而我們只需要在 service 的實(shí)現(xiàn)類中通過匿名類的方式實(shí)現(xiàn)第3步:【構(gòu)造查詢條件】設(shè)定即可!