Spring Boot JPA-Repository方法名查询推导
Repository的默认实现
1.Repository接口的默认实现是 SimpleJpaRepository ,以及他的扩展实现 QueryDslJpaRepository 其中,QueryDslJpaRepository不在我们这篇文章讨论范围内。
重点关注SimpleJpaRepository,它实现了 JpaRepository 和JpaSpecificationExecutor 两个接口。这俩接口为我们提供了一些常用的仓库接口(JpaRepository ,PagingAndSortingRepository ,CrudRepository )内各个方法的默认实现方式,它们的关系是依次继承。观察这些实现方法,发现并没有我们要的仓库中方法名查询推导(Query Derivation from method names)就像这样的:
User findByNameAndEmail(String name, String email);
虽然没有实现,但是JPA却能够出色的完成它的工作。由此,有了我们这篇文章所要探讨的问题:JPA是如何根据方法名以及传入参数,完成SQL拼装,最后返回指定数据类型的。
初始化Repository
首先得知道Repository仓库的工厂bean在哪里,才能知道它具体操作实体的方法。在 @EnableJpaRepositories (一个用于Srping JPA的代码配置,用于取代xml形式的配置文件的注解)注解中看到,注解会默认注册 JpaRepositoryFactoryBean.class 作为Repository仓库的工厂bean。
继续跟进去, 从 JpaRepositoryFactoryBean 的源码中可以看到它的继承结构:
查阅了资料发现,transactionalRepositoryFactoryBeanSupport
和RepositoryFactorytBeanSupport
来自于spring-data-commons项目(这个项目下次再去抽时间研究)。
FactoryBean.getObject()
方法由RepositoryFactoryBeanSupport
类实现,对仓库的初始化通过afterPropertiesSet()
实现了懒加载。其中,this.factory的获取方法由抽象方法RepositoryFactorySupport
类的createRepositoryFactory()
方法提供。逐渐接近Repository仓库的工厂bean了。继续查看这个抽象方法的实现,在TransactionalRepositoryFactoryBeanSupport
中可以看到,createRepositoryFactory()
方法被实现并被标记为final
。从实现中可以看到它又调用了抽象方法doCreateRepositoryFactory()
来获取RepositoryFactorySuport
,然后为该factory增加了exceptionPostProcessor
和txPostProcessor
两个PostProcessor进行代理包装。从名称上可以获悉这两个PostProcessor分别负责处理例外和进行事务管理。
protected final RepositoryFactorySupport createRepositoryFactory() {
RepositoryFactorySupport factory = this.doCreateRepositoryFactory();
RepositoryProxyPostProcessor exceptionPostProcessor = this.exceptionPostProcessor;
if (exceptionPostProcessor != null) {
factory.addRepositoryProxyPostProcessor(exceptionPostProcessor);
}
RepositoryProxyPostProcessor txPostProcessor = this.txPostProcessor;
if (txPostProcessor != null) {
factory.addRepositoryProxyPostProcessor(txPostProcessor);
}
return factory;
}
接着看这个获取factory所调用的方法,JpaRepositoryFactoryBean.doCreateRepositoryFactory()
,这个方法用注入的entityManager来new了一个了一个JpaRepositoryFactory
protected RepositoryFactorySupport doCreateRepositoryFactory() {
Assert.state(this.entityManager != null, "EntityManager must not be null!");
// 用注入的entityManager去new 一个JpaRepositoryFactory
return this.createRepositoryFactory(this.entityManager);
}
protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {
JpaRepositoryFactory jpaRepositoryFactory = new JpaRepositoryFactory(entityManager);
jpaRepositoryFactory.setEntityPathResolver(this.entityPathResolver);
return jpaRepositoryFactory;
}
在JpaRepositoryFactoryBean
中可以看到,doCreateRepositoryFactory
方法被实现。该方法使用注入的EntityManager
来new了一个JpaRepositoryFactory
工厂类。
JpaRepositoryFactory
这个没有Bean的工厂类继承了RepositoryFactorySupport
类(在spring-data-commons项目中),和JpaRepositoryFactoryBean
的继承结构如出一辙。在RepositoryFactorySupport
这个类的源代码中,我们可以找到getRepository
方法的实现。
public <T> T getRepository(Class<T> repositoryInterface, RepositoryFragments fragments) {
if (LOG.isDebugEnabled()) {
LOG.debug("Initializing repository instance for {}…", repositoryInterface.getName());
}
Assert.notNull(repositoryInterface, "Repository interface must not be null!");
Assert.notNull(fragments, "RepositoryFragments must not be null!");
RepositoryMetadata metadata = this.getRepositoryMetadata(repositoryInterface);
RepositoryComposition composition = this.getRepositoryComposition(metadata, fragments);
RepositoryInformation information = this.getRepositoryInformation(metadata, composition);
// 以上均为获取Repository的各种信息
this.validate(information, composition);
Object target = this.getTargetRepository(information);
// 开始创建代理并且增加Advice对返回数据进行统一包装
ProxyFactory result = new ProxyFactory();
result.setTarget(target);
result.setInterfaces(new Class[]{repositoryInterface, Repository.class, TransactionalProxy.class});
if (MethodInvocationValidator.supports(repositoryInterface)) {
result.addAdvice(new MethodInvocationValidator());
}
result.addAdvice(SurroundingTransactionDetectorMethodInterceptor.INSTANCE);
result.addAdvisor(ExposeInvocationInterceptor.ADVISOR);
this.postProcessors.forEach((processor) -> {
processor.postProcess(result, information);
});
result.addAdvice(new DefaultMethodInvokingMethodInterceptor());
ProjectionFactory projectionFactory = this.getProjectionFactory(this.classLoader, this.beanFactory);
// 进行查询推导的地方
result.addAdvice(new RepositoryFactorySupport.QueryExecutorMethodInterceptor(information, projectionFactory));
composition = composition.append(RepositoryFragment.implemented(target));
result.addAdvice(new RepositoryFactorySupport.ImplementationMethodExecutionInterceptor(composition));
T repository = result.getProxy(this.classLoader);
if (LOG.isDebugEnabled()) {
LOG.debug("Finished creation of repository instance for {}.", repositoryInterface.getName());
}
return repository;
}
没错,关键缺失的一环就在这里。在这个方法中可以看到,首先它获取了Repository的各种信息,包括metadata、customImplementation、information等, 然后使用了ProxyFactory
,并且为这个ProxyFactory
注册了几个Advice
,最后通过getProxy
方法创建了一个Repository的代理。 而真正起到魔法般作用的应该就是这个QueryExecutorMethodInterceptor
类了。
核心的PartTreeJpaQuery
前面的debug调用过程略过,直接讲最核心的这个PartTreeJpaQuery,在开始之前有个预判,其实现方式是是采用了某种巧妙的数据结构完成了标准的拆分,最后生成SQL语句。
关于PartTreeJpaQuery
,其继承结构如图:
PartTreeJpaQuery
扩展自AbstractJpaQuery
抽象类,并且AbstractJpaQuery
实现了RepositoryQuery
接口。
继续看PartTreeJpaQuery
实现,从该类的注释中我们获知,该类是基于PartTree
的一个AbstractJpaQuery
的实现。
AbstractJpaQuery implementation based on a PartTree.
这大概率就是我们要找的那个巧妙的数据结构了,查了下这个类的介绍如下:
Class to parse a String into a tree or PartTree.OrParts consisting of simple Part instances in turn. Takes a domain class as well to validate that each of the Parts are referring to a property of the domain class. The PartTree can then be used to build queries based on its API instead of parsing the method name for each query execution.
意思就是解析字符串到Tree 或PartTree。或部分依次由简单的部分实例组成。需要域类,以验证每个部件都指域类的属性。然后,PartTree可用于基于其 API 构建查询,而不是解析每个查询执行的方法名称。如下:
在Subject类
(主语类)中
Assert.notNull(source, "Source must not be null");
Assert.notNull(domainClass, "Domain class must not be null");
Matcher matcher = PREFIX_TEMPLATE.matcher(source);
if (!matcher.find()) {
this.subject = new PartTree.Subject(Optional.empty());
this.predicate = new PartTree.Predicate(source, domainClass);
} else {
this.subject = new PartTree.Subject(Optional.of(matcher.group(0)));
this.predicate = new PartTree.Predicate(source.substring(matcher.group().length()), domainClass);
}
private static final String DISTINCT = "Distinct";
private static final Pattern COUNT_BY_TEMPLATE = Pattern.compile("^count(\\p{Lu}.*?)??By");
private static final Pattern EXISTS_BY_TEMPLATE = Pattern.compile("^(exists)(\\p{Lu}.*?)??By");
private static final Pattern DELETE_BY_TEMPLATE = Pattern.compile("^(delete|remove)(\\p{Lu}.*?)??By");
private static final String LIMITING_QUERY_PATTERN = "(First|Top)(\\d*)?";
private static final Pattern LIMITED_QUERY_TEMPLATE = Pattern.compile("^(find|read|get|query|stream)(Distinct)?(First|Top)(\\d*)?(\\p{Lu}.*?)??By");
private final boolean distinct;
private final boolean count;
private final boolean exists;
private final boolean delete;
private final Optional<Integer> maxResults;
public Subject(Optional<String> subject) {
this.distinct = (Boolean)subject.map((it) -> {
return it.contains("Distinct");
}).orElse(false);
this.count = this.matches(subject, COUNT_BY_TEMPLATE);
this.exists = this.matches(subject, EXISTS_BY_TEMPLATE);
this.delete = this.matches(subject, DELETE_BY_TEMPLATE);
this.maxResults = this.returnMaxResultsIfFirstKSubjectOrNull(subject);
}
private Optional<Integer> returnMaxResultsIfFirstKSubjectOrNull(Optional<String> subject) {
return subject.map((it) -> {
Matcher grp = LIMITED_QUERY_TEMPLATE.matcher(it);
return !grp.find() ? null : StringUtils.hasText(grp.group(4)) ? Integer.valueOf(grp.group(4)) : 1;
});
}
通过阅读代码,我们知道该类是通过正则表达式,将一串方法名分解成主语(Subject对象),谓语(Predicate对象)以及(宾语)OrPart对象通过构建一个语法树放在内存中,待进行查询时候则执行此条SQL语句。举个例子:
findDistinctUserByIdOrderByName
通过正则表达式解析,分成主语部分DistinctUserBy
和谓语部分OrderByName
。 在Subject
类(主语类)中,还将继续通过正则表达式进行分解,提取法语中distinct
、count
、delete
和maxResults
几种属性。
在Predicate
类(谓语类)中
public Predicate(String predicate, Class<?> domainClass) {
String[] parts = PartTree.split(this.detectAndSetAllIgnoreCase(predicate), "OrderBy");
if (parts.length > 2) {
throw new IllegalArgumentException("OrderBy must not be used more than once in a method name!");
} else {
this.nodes = (List)Arrays.stream(PartTree.split(parts[0], "Or")).filter(StringUtils::hasText).map((part) -> {
return new PartTree.OrPart(part, domainClass, this.alwaysIgnoreCase);
}).collect(Collectors.toList());
this.orderBySource = parts.length == 2 ? new OrderBySource(parts[1], Optional.of(domainClass)) : OrderBySource.EMPTY;
}
}
方法名中有OrderBy
则首先对该方法名使用OrderBy
进行分割, 再然后针对分割后不包含OrderBy
的部分,通过关键字Or
进行分割。将对Or关键字分割后的部分分别包装成OrPart对象作为节点,(Name)
则被包装成OrderBySource
对象另行对待。举个例子:
NameAndAgeOrCorpAndDeptOrderById
通过分割后变成
(NameAndAge)Or(CorpAndDept)OrderBy(Id)
在OrPart
中
private final List<Part> children;
OrPart(String source, Class<?> domainClass, boolean alwaysIgnoreCase) {
String[] split = PartTree.split(source, "And");
this.children = (List)Arrays.stream(split).filter(StringUtils::hasText).map((part) -> {
return new Part(part, domainClass, alwaysIgnoreCase);
}).collect(Collectors.toList());
}
public Iterator<Part> iterator() {
return this.children.iterator();
}
public String toString() {
return StringUtils.collectionToDelimitedString(this.children, " and ");
}
}
我们可以看到,它再次使用正则表达式,使用And
关键字对已分割的OrPart
进行分割,最后包装成Part
对象作为子节点。 这样来说的话,其实在Predicate
类中,实际上是已经构建了一颗语法树。还是拿之前的例子来说的话, 如图所示:
可以看到,Name
和Age
、Corp
和Dept
,分别属于不同的上一级子节点,并且每一个叶节点均为实体类的属性,这也为JPSQL
(直接查询Bean实体的SQL)的实现提供基础。
到此为止,针对Repository
中定义的方法名称创建查询的解析就已经做完了。完成后的解析会以PartTreeJpaQuery
对象的方式放置 在内存中。 待到相应的查询执行时,就会从内存中取出并执行相应的查询。
需要特别注意的是,这些放置在内存中的PartTreeJpaQuery
会在初始化阶段(构造器中)去创建相应的JPA CriteriaQuery
, 所以如果我们在Repository
接口中不小心定义了一个错误的方法名,在Spring容器启动时就应该能看到报错了。