使用方法
spring data jpa其实体Repository继承Repository接口,如JpaRepository等接口,也可以选择JpaSpecificationExecutor,这样就可以使用方法名称,来定义查询
如
List<User> findByEmailAddressAndLastname(EmailAddress emailAddress, String lastname)
List<User> findDistinctPeopleByLastnameOrFirstname(String lastname, String firstname)
List<User> findPeopleDistinctByLastnameOrFirstname(String lastname, String firstname)
List<User> findByLastnameIgnoreCase(String lastname)
List<User> findByLastnameAndFirstnameAllIgnoreCase(String lastname, String firstname)
List<User> findByLastnameOrderByFirstnameAsc(String lastname)
可以查找 关键字对应的枚举在哪个类里面
实现原理概述
上面的这种通过方法名称定义查询底层是如何实现的呢?
其实现原理是采用动态代理的机制,有两种定义查询方法:从方法名称中指定,或通过@Query手动定义的查询。
查询生成器
内部基础架构中有个根据方法名生成查询的查询生成器机制。其将查询抽象为RepositoryQuery这个interface,其实现类是PartTreeJpaQuery,PartTreeJpaQuery关联PartTree
package org.springframework.data.repository.query.parser;
public class PartTree implements Iterable<OrPart>{
private static final String QUERY_PATTERN = "find|read|get|query|stream";
private static final String COUNT_PATTERN = "count";
private static final String EXISTS_PATTERN = "exists";
private static final String DELETE_PATTERN = "delete|remove";
}
查询生成器不仅支持单一的属性定义约束,而且支持遍历嵌套属性定义约束。举一个复杂的例子来说明:
假设一个Person实体对象里面有一个Address属性里面包含一个ZipCode属性
List<Person> findByAddressZipCode(String zipCode)
创建及查找的过程是:
- 解析算法首先将整个part(AddressZipCode)解释为属性,并使用该名称(uncapitalized)检查实体类的属性。如果算法成功,就使用该属性。
- 如果不是,就拆分右侧驼峰部分的信号源到头部和尾部,并试图找到相应的属性,AddressZip和Code。如果算法找到一个具有头部的属性,那么它需要尾部,并从那里继续构建树,然后按照刚刚描述的方式将尾部分割。
- 如果第一个分割不匹配,那将分割点移动到左边(Address,ZipCode),然后继续。
如果Person类也有一个addressZip属性,那会产生歧义。可以使用下面的方式来指定实体类的属性
List<Person> findByAddress_ZipCode(ZipCode zipCode)
可以到PartTreeJpaQuery.java中查询一下相关method的name拆分和实现逻辑
找出源码后,设置一个断点,就可以进行代码分析了
拦截功能逻辑
总结来说,就是通过拦截器,在真正执行查询操作前,将查询语法通过选择的对应的策略进行解析。解析完成后再去进行真正的查询
创建repository proxy
org.springframework.beans.factory.FactoryBean
Spring的FactoryBean接口,说明其是一个工厂,其职责是#getObject来生成Bean
RepositoryFactoryBeanSupport实现了FactoryBean接口
public abstract class RepositoryFactoryBeanSupport<T extends Repository<S, ID>, S, ID extends Serializable> implements
InitializingBean, RepositoryFactoryInformation<S, ID>, FactoryBean<T>, BeanClassLoaderAware, BeanFactoryAware {
public T getObject() {
return initAndReturn();
}
private T initAndReturn() {
// Returns the previously initialized repository proxy or creates and returns the proxy if previously uninitialized
// 返回初始化的字节码提升后的repository proxy对象
this.repository = this.factory.getRepository(repositoryInterface, customImplementation);
return this.repository;
}
}
org.springframework.data.repository.core.support.RepositoryFactorySupport
创建代理对象
public abstract class RepositoryFactorySupport implements BeanClassLoaderAware, BeanFactoryAware {
public <T> T getRepository(Class<T> repositoryInterface, Object customImplementation) {
RepositoryMetadata metadata = getRepositoryMetadata(repositoryInterface);
// Create proxy
ProxyFactory result = new ProxyFactory();
result.setTarget(target);
result.setInterfaces(new Class[] { repositoryInterface, Repository.class });
result.addAdvice(ExposeInvocationInterceptor.INSTANCE);
if (TRANSACTION_PROXY_TYPE != null) {
result.addInterface(TRANSACTION_PROXY_TYPE);
}
for (RepositoryProxyPostProcessor processor : postProcessors) {
processor.postProcess(result, information);
}
if (IS_JAVA_8) {
result.addAdvice(new DefaultMethodInvokingMethodInterceptor());
}
// 添加QueryExecutorMethodInterceptor
result.addAdvice(new QueryExecutorMethodInterceptor(information, customImplementation, target));
// #getProxy
return (T) result.getProxy(classLoader);
}
}
其中getProxy会调用到
org.springframework.aop.framework.DefaultAopProxyFactory#createAopProxy
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
// Jdk代理
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
// cglib代理
return new ObjenesisCglibAopProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}
方法拦截器
org.aopalliance.intercept.MethodInterceptor
spring bean的方法拦截器接口
查询拦截器
org.springframework.data.repository.core.support.RepositoryFactorySupport.QueryExecutorMethodInterceptor#QueryExecutorMethodInterceptor
public class QueryExecutorMethodInterceptor implements MethodInterceptor {
// invoke方法
private Object doInvoke(MethodInvocation invocation) throws Throwable {
Method method = invocation.getMethod();
Object[] arguments = invocation.getArguments();
if (isCustomMethodInvocation(invocation)) {
Method actualMethod = repositoryInformation.getTargetClassMethod(method);
return executeMethodOn(customImplementation, actualMethod, arguments);
}
// 如果有@Query注解 nativeSql
if (hasQueryFor(method)) {
return queries.get(method).execute(arguments);
}
// Lookup actual method as it might be redeclared in the interface
// and we have to use the repository instance nevertheless
Method actualMethod = repositoryInformation.getTargetClassMethod(method);
return executeMethodOn(target, actualMethod, arguments);
}
}
QueryExecutorMethodInterceptor实现了MethodInterceptor接口,当一个Repository上的查询方法(比如findByLastname)被调用时,Advice拦截器会在方法真正地实现调用前先执行MethodInterceptor的invoke方法。这样我们就有机会在真正方法实现执行前执行其他的代码了。
对于QueryExecutorMethodInterceptor来说,最重要的代码并不在invoke方法中,而是在它的构造器
/**
* Creates a new {@link QueryExecutorMethodInterceptor}. Builds a model of {@link QueryMethod}s to be invoked on
* execution of repository interface methods.
*/
public QueryExecutorMethodInterceptor(RepositoryInformation repositoryInformation, Object customImplementation,Object target) {
this.resultHandler = new QueryExecutionResultHandler();
this.repositoryInformation = repositoryInformation;
this.customImplementation = customImplementation;
this.target = target;
// 拦截器使用策略模式来查找对应的查询
// 由JpaQueryLookupStrategy提供的策略,具体表现为一个基类3个具体实现
// AbstractQueryLookupStrategy
// CreateIfNotFoundQueryLookupStrategy
// DeclaredQueryLookupStrategy
// CreatorQueryLookupStrategy
QueryLookupStrategy lookupStrategy = getQueryLookupStrategy(queryLookupStrategyKey, RepositoryFactorySupport.this.evaluationContextProvider);
lookupStrategy = lookupStrategy == null ? getQueryLookupStrategy(queryLookupStrategyKey) : lookupStrategy;
Iterable<Method> queryMethods = repositoryInformation.getQueryMethods();
SpelAwareProxyProjectionFactory factory = new SpelAwareProxyProjectionFactory();
factory.setBeanClassLoader(classLoader);
factory.setBeanFactory(beanFactory);
// 使用QueryLookupStrategy,针对Repository接口上的方法查询Query
for (Method method : queryMethods) {
// 查找到的查询被抽象为一个RepositoryQuery接口
// 实际实现为PartTreeJpaQuery,该实现负责解析之定义Repository接口上的方法名并将之转化为对应的查询,其关联PartTree对象,负责以树状结构表达Repository接口上方法名所代表的语法结构
RepositoryQuery query = lookupStrategy.resolveQuery(method, repositoryInformation, factory, namedQueries);
invokeListeners(query);
queries.put(method, query);
}
}
总结
总结来说,spring应用上下文会自动将repository相关的类注册为spring bean,在注册的时候,通过RepositoryFactoryBeanSupport#getObject获取bean对象的时候,就将addAdvice(QueryExecutorMethodInterceptor),这样当访问此repository的方法时,会先执行切点的拦截器,做方法名称的解析,封装到RepositoryQuery interface中