Spring Boot JPA-Repository方法名查询推导

Spring Boot JPA-Repository方法名查询推导

  1. Repository的默认实现

  2. 初始化Repository
  3. 核心的PartTreeJpaQuery

Repository的默认实现

1.Repository接口的默认实现是 SimpleJpaRepository ,以及他的扩展实现 QueryDslJpaRepository 其中,QueryDslJpaRepository不在我们这篇文章讨论范围内。
重点关注SimpleJpaRepository,它实现了 JpaRepositoryJpaSpecificationExecutor 两个接口。这俩接口为我们提供了一些常用的仓库接口(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 的源码中可以看到它的继承结构:
在这里插入图片描述

查阅了资料发现,transactionalRepositoryFactoryBeanSupportRepositoryFactorytBeanSupport来自于spring-data-commons项目(这个项目下次再去抽时间研究)。

FactoryBean.getObject()方法由RepositoryFactoryBeanSupport类实现,对仓库的初始化通过afterPropertiesSet()实现了懒加载。其中,this.factory的获取方法由抽象方法RepositoryFactorySupport类的createRepositoryFactory()方法提供。逐渐接近Repository仓库的工厂bean了。继续查看这个抽象方法的实现,在TransactionalRepositoryFactoryBeanSupport中可以看到,createRepositoryFactory()方法被实现并被标记为final。从实现中可以看到它又调用了抽象方法doCreateRepositoryFactory()来获取RepositoryFactorySuport,然后为该factory增加了exceptionPostProcessortxPostProcessor两个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接口。

Hierarchy class PartTreeJpaQuery

继续看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.

意思就是解析字符串到TreePartTree。或部分依次由简单的部分实例组成。需要域类,以验证每个部件都指域类的属性。然后,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类(主语类)中,还将继续通过正则表达式进行分解,提取法语中distinctcountdeletemaxResults几种属性。

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类中,实际上是已经构建了一颗语法树。还是拿之前的例子来说的话, 如图所示:

在这里插入图片描述

可以看到,NameAgeCorpDept,分别属于不同的上一级子节点,并且每一个叶节点均为实体类的属性,这也为JPSQL(直接查询Bean实体的SQL)的实现提供基础。

到此为止,针对Repository中定义的方法名称创建查询的解析就已经做完了。完成后的解析会以PartTreeJpaQuery对象的方式放置 在内存中。 待到相应的查询执行时,就会从内存中取出并执行相应的查询。

需要特别注意的是,这些放置在内存中的PartTreeJpaQuery会在初始化阶段(构造器中)去创建相应的JPA CriteriaQuery, 所以如果我们在Repository接口中不小心定义了一个错误的方法名,在Spring容器启动时就应该能看到报错了。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
spring-boot-starter-jpaspring-boot-starter-jdbc是Spring Boot框架中用于数据库访问的两个常用依赖库,它们在数据库访问方式和功能上有一些区别。 spring-boot-starter-jpa是基于Java Persistence API(JPA)的依赖库,它提供了一种面向对象的方式来进行数据库访问。JPA是Java EE的一部分,它定义了一套标准的API和规范,用于实现对象关系映射(ORM)。使用spring-boot-starter-jpa可以方便地进行实体类与数据库表之间的映射,通过简单的注解和配置,可以实现数据库的增删改查操作。同时,spring-boot-starter-jpa还提供了一些高级特性,如事务管理、缓存等。 相比之下,spring-boot-starter-jdbc是基于Java Database Connectivity(JDBC)的依赖库。JDBC是Java语言访问关系型数据库的标准API,它提供了一套用于执行SQL语句和处理结果集的方法。使用spring-boot-starter-jdbc可以直接使用JDBC API进行数据库操作,需要手动编写SQL语句和处理结果集。相对于JPA,JDBC更加底层,更加灵活,可以直接操作数据库的细节。 总结一下区别: 1. 数据库访问方式:spring-boot-starter-jpa使用面向对象的方式进行数据库访问,而spring-boot-starter-jdbc使用基于SQL的方式进行数据库访问。 2. 抽象程度:spring-boot-starter-jpa提供了更高级的抽象,通过注解和配置可以实现对象关系映射,而spring-boot-starter-jdbc需要手动编写SQL语句和处理结果集。 3. 功能特性:spring-boot-starter-jpa提供了一些高级特性,如事务管理、缓存等,而spring-boot-starter-jdbc相对较为简单,功能相对较少。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值