Spring data jpa的高階查詢的應用和底層原理分析
spring data jpa的查詢
目前比較簡單的查詢:
三種查詢方案的寫法
- 固定引數查詢
interface XxxRepo implements JpaRepository<T,Long>{
EntityXxx findByNameAndSex(String name,String sex);
}
這種方式是簡單的,方法名錶達自己想查詢的方法。支援and, or ,like, top, betweent,order等等。
- 不定引數個數查詢
PageRequest pageable = new PageRequest(query.getPindex(), query. getPcount(), query.getSortObj());//分頁
Example<EntityXXX> example = Example.of(entity, ExampleMatcher.matchingAll());//封裝物件,matchingAll表明滿足所有條件
Page<EntityXXX> findAll = entityRepo.findAll(example, pageable);//開始查詢並返回物件
像以上這種程式碼,是可以多條件and查詢的。比如name=abc and sex=male。這種組合滿足我們一般的需求。
但是如果,我們想要查詢name in (zhangsan,lisi,wanger,mazi)等,那麼這種方式就不再適用。我們可以採用高階一點的查詢。
- 高階組合查詢:
public Page<EntityXxx> findAll(EntityXxx entity, PageRequest pageable) {
Specification<EntityXxx> condition = whereCondition(entity);
Page<EntityXxx> page = entityRepo.findAll(condition, pageable);
return page;
}
private Specification<EntityXxx> whereCondition (EntityXxx entity) {
return new Specification<EntityXxx>() {
@Override
public Predicate toPredicate(Root<EntityXxx> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
List<Predicate> predicates = new ArrayList<Predicate>();
List<String> names = new ArrayList<>();
names.add("zhangsan");
names.add("lisi");
names.add("wanger");
Expression<Long> parentExpression = root.get("name");
Predicate parentPredicate = parentExpression.in(names);
predicates.add(parentPredicate);
if (!StringUtils.isEmpty(entity.getSex())) {
predicates.add(cb.like(root.<String>get("sex"), "%" + entity.getSex() + "%"));//like查詢
}
if (null != entity.getIdnum()) {
predicates.add(cb.equal(root.<Integer>get("innum"), entity.getIdnum()));//精準匹配
}
return query.where(predicates.toArray(new Predicate[predicates.size()])).getRestriction();
}
};
}
這種就比較高階了,判斷一下前端傳過來的物件裡某某屬性是否有值,有的話就新增到條件裡。
這樣一來,我們就可以簡單的寫個方法滿足很大部分的要求。
當然,如果我們只停留在會用,而不清楚原理的話,對個人發展是不利的。
我們可以來看看,每一種情況下,spring是怎樣實現的。
三種情況的Spring底層實現:
其實實現都是類似
類似這種,定義一個介面EntityRepo implements JpaRepository,然後在裡邊寫各種簡單的查詢。這種執行時Spring是通過方法攔截器實現的。在Spring Data(commons包)裡的實現:
a. 在應用啟動時收集所有repo裡的所有方法
//RepositoryFactorySupport$QueryExecutorMethodInterceptor
public class QueryExecutorMethodInterceptor implements MethodInterceptor {
//表明該方法屬於哪種查詢:
//例如:對於方法findByNameAndSex
// 1.SimpleJpaQuery
// 方法頭上@Query註解的nativeQuery屬性預設值為false,也就是使用JPQL,此時會建立SimpleJpaQuery例項,並通過兩個StringQuery類例項分別持有query jpql語句和根據query jpql計算拼接出來的countQuery jpql語句;
// 2.NativeJpaQuery
// 方法頭上@Query註解的nativeQuery屬性如果顯式的設定為nativeQuery=true,也就是使用原生SQL,此時就會建立NativeJpaQuery例項;
// 3.PartTreeJpaQuery
// 方法頭上未進行@Query註解,將使用spring-data-jpa獨創的方法名識別的方式進行sql語句拼接,此時在spring-data-jpa內部就會建立一個PartTreeJpaQuery例項;
// 4.NamedQuery
// 使用javax.persistence.NamedQuery註解訪問資料庫的形式,此時在spring-data-jpa內部就會根據此註解選擇建立一個NamedQuery例項;
// 5.StoredProcedureJpaQuery
// 顧名思義,在Repository介面的方法頭上使用org.springframework.data.jpa.repository.query.Procedure註解,也就是呼叫儲存過程的方式訪問資料庫,此時在spring-data-jpa內部就會根據@Procedure註解而選擇建立一個StoredProcedureJpaQuery例項。
private final Map < Method, RepositoryQuery > queries;
...
public QueryExecutorMethodInterceptor(RepositoryInformation repositoryInformation,
ProjectionFactory projectionFactory) {
...
this.queries = lookupStrategy //
.map(it - > mapMethodsToQuery(repositoryInformation, it, projectionFactory)) //
.orElse(Collections.emptyMap());
}
private Map < Method, RepositoryQuery > mapMethodsToQuery(RepositoryInformation repositoryInformation,
QueryLookupStrategy lookupStrategy, ProjectionFactory projectionFactory) {
return repositoryInformation.getQueryMethods().stream() //
.map(method - > lookupQuery(method, repositoryInformation, lookupStrategy, projectionFactory)) //
.peek(pair - > invokeListeners(pair.getSecond())) //
.collect(Pair.toMap());
}
...
}
...
}
查詢repository裡有哪些方法:
這個方法裡對每個repo裡的方法進行收集。
class DefaultRepositoryInformation{
...
public Streamable<Method> getQueryMethods() {
Set<Method> result = new HashSet<>();
for (Method method : getRepositoryInterface().getMethods()) {
method = ClassUtils.getMostSpecificMethod(method, getRepositoryInterface());
if (isQueryMethodCandidate(method)) {
result.add(method);
}
}
return Streamable.of(Collections.unmodifiableSet(result));
}
}
...
b. 當程式執行時遇到呼叫repo中的某個方法時:
同樣檢視那個攔截器裡的程式碼。
//RepositoryFactorySupport$QueryExecutorMethodInterceptor implements MethodInterceptor
@Nullable
private Object doInvoke(MethodInvocation invocation) throws Throwable {
Method method = invocation.getMethod();
Object[] arguments = invocation.getArguments();
if (hasQueryFor(method)) {
return queries.get(method).execute(arguments);//execute執行的是AbstractJpaQuery中的方法
}
return invocation.proceed();
}
找到AbstractJpaQuery
@Nullable
@Override
public Object execute(Object[] parameters) {
return doExecute(getExecution(), parameters);
}
找到PartTreeJpaQuery extends AbstractJpaQuery
為什麼是這個,這是因為我們在使用自定義的查詢方法(如:EntityXxx findByNameAndSex(String name,String sex)),使用的是這個Query。這個在上面的程式碼裡註釋有所介紹。
@Override
protected JpaQueryExecution getExecution() {
if (this.tree.isDelete()) {
return new DeleteExecution(em);
} else if (this.tree.isExistsProjection()) {
return new ExistsExecution();
}
return super.getExecution();
}
在AbstractJpaQuery找到對應的執行器。
protected JpaQueryExecution getExecution() {
if (method.isStreamQuery()) {
return new StreamExecution();
} else if (method.isProcedureQuery()) {
return new ProcedureExecution();//儲存過程執行器
} else if (method.isCollectionQuery()) {
return new CollectionExecution();//結果為集合
} else if (method.isSliceQuery()) {
return new SlicedExecution(method.getParameters());
} else if (method.isPageQuery()) {
return new PagedExecution(method.getParameters());//分頁查詢
} else if (method.isModifyingQuery()) {
return new ModifyingExecution(method, em);//方法為修改資料庫
} else {
return new SingleEntityExecution();//結果為單個
}
}
然後找到對應的執行器類,並在裡邊找對應的方法。
關於裡邊的內容盤根錯節,無法用簡單文字詳細說明。這裡只是提供一個實實在在的線頭,你根據這個線頭就能剝開了。_
--------------------2018.9.20更新----------------------------------------------------
對於第二種或第三種查詢,Spring原始碼分析:
//第二種
Page<EntityXXX> findAll = exdapRepo.findAll(example, pageable)
//呼叫SimpleJpaRepository
public <S extends T> Page<S> findAll(Example<S> example, Pageable pageable) {
ExampleSpecification<S> spec = new ExampleSpecification<S>(example);
Class<S> probeType = example.getProbeType();
TypedQuery<S> query = getQuery(new ExampleSpecification<S>(example), probeType, pageable);
return pageable == null ? new PageImpl<S>(query.getResultList()) : readPage(query, probeType, pageable, spec);
}
protected <S extends T> TypedQuery<S> getQuery(Specification<S> spec, Class<S> domainClass, Pageable pageable) {
Sort sort = pageable == null ? null : pageable.getSort();
return getQuery(spec, domainClass, sort);
}
//第三種
Page<EntityXxx> page = entityRepo.findAll(condition, pageable);//condition:Specification型別
//呼叫SimpleJpaRepository
public Page<T> findAll(Specification<T> spec, Pageable pageable) {
TypedQuery<T> query = getQuery(spec, pageable);
return pageable == null ? new PageImpl<T>(query.getResultList())
: readPage(query, getDomainClass(), pageable, spec);
}
protected TypedQuery<T> getQuery(Specification<T> spec, Pageable pageable) {
Sort sort = pageable == null ? null : pageable.getSort();
return getQuery(spec, getDomainClass(), sort);
}
看到沒有,兩者殊途同歸,最終呼叫的都是getQuery:
protected <S extends T> TypedQuery<S> getQuery(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 != null) {
query.orderBy(toOrders(sort, root, builder));
}
return applyRepositoryMethodMetadata(em.createQuery(query));
}
然後,我們順著getQuery往下擼,重點關注applySpecificationToCriteria:
private <S, U extends T> Root<U> applySpecificationToCriteria(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;
}
如果我們在查詢的時候new一個匿名Specification的實現,那麼這裡會直接返回。否則,這裡會例項一個ExampleSpecification。
其toPredicate的Spring實現:
public static <T> Predicate getPredicate(Root<T> root, CriteriaBuilder cb, Example<T> example) {
Assert.notNull(root, "Root must not be null!");
Assert.notNull(cb, "CriteriaBuilder must not be null!");
Assert.notNull(example, "Example must not be null!");
ExampleMatcher matcher = example.getMatcher();
List<Predicate> predicates = getPredicates("", cb, root, root.getModel(), example.getProbe(),
example.getProbeType(), new ExampleMatcherAccessor(matcher), new PathNode("root", null, example.getProbe()));
if (predicates.isEmpty()) {
return cb.isTrue(cb.literal(true));
}
if (predicates.size() == 1) {
return predicates.iterator().next();
}
Predicate[] array = predicates.toArray(new Predicate[predicates.size()]);
return matcher.isAllMatching() ? cb.and(array) : cb.or(array);
}
就介紹到這裡了吧。感覺仔細看下,應該可以看懂。如果有不明白的地方,或者覺得我寫的不好,可以留言,我盡力更正改進。