Spring data jpa的高级查询的应用和底层原理分析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/bbbbln/article/details/82776213

spring data jpa的查询

目前比较简单的查询:

三种查询方案的写法

  1. 固定参数查询
interface XxxRepo implements JpaRepository<T,Long>{
	EntityXxx findByNameAndSex(String name,String sex);
}

这种方式是简单的,方法名表达自己想查询的方法。支持and, or ,like, top, betweent,order等等。

  1. 不定参数个数查询
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)等,那么这种方式就不再适用。我们可以采用高级一点的查询。

  1. 高级组合查询:
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);
}

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

展开阅读全文

没有更多推荐了,返回首页