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);
}
就介绍到这里了吧。感觉仔细看下,应该可以看懂。如果有不明白的地方,或者觉得我写的不好,可以留言,我尽力更正改进。