spring data jpa通过方法名称定义查询的实现机制解析

使用方法

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中

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值