Spring data JPA实践和原理浅析
一、前言
从我们进⼊编程的世界,成为程序员到现在为⽌,总有⼏个感觉神奇和激动的时刻,其中肯定包括你第⼀次程序连上数据库可以实现 CURD 功能的时候,就算那时的我们写着千遍⼀律 JDBC 模板代码也是乐此不疲。
时代在不断进步,技术也不断在发展,市面上已经有很多优秀的数据库持久化框架供我们使用,今天我将带大家来了解JPA的使用。

二、基本使用
在我们现在Spring Boot横行无忌的时代,在项目中引入JPA非常简单,我们以Maven以及常用的MySQL数据库为例
在pom.xml文件添加以下依赖
<parent><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId><version>2.5.6</version><relativePath/></parent><!-- jpa --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency><!-- mysql --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency>
在Spring Boot的YML文件中添加以下内容
spring:datasource:driver-class-name:com.mysql.cj.jdbc.Driverurl:jdbc:mysql://localhost:3306/sakila?useUnicode=true&zeroDateTimeBehavior=convertToNull&autoReconnect=true&characterEncoding=utf-8 username: rootpassword: root
定义Entity类,我测试过程使用的数据源为MySQL官方提供的样例数据库sakila,大家可以在https://dev.mysql.com/doc/index-other.html自行下载
@Entity@Data // lombok注解public class Actor {@Id// Column注解不是必须的,如果满足字段驼峰形式-
// 与数据库字段以下划线分隔形式对应即可 -
@Column(name = "actor_id", nullable = false) -
private Integer actorId; @Column(name = "first_name", nullable = false, length = 45)private String firstName;private String lastName;private Timestamp lastUpdate;}
定义Respository接口,一般我们通过继承JpaRepository接口即能满足我们一般的CURD操作,如果需要支持复杂逻辑查询
比如:动态SQL;联表查询,则需要继承 JpaSpecificationExecutor 接口,并配合Specification的接口方法。
@Repositorypublic interface ActorRepository extends JpaRepository<Actor, Integer> {}
在JpaRepository的中我们可以看到有findAll、getById、findById、save、saveAll、deleteById…这些默认定义,这是JPA为我们提供的常用的一些数据操作,我们可以直接使用,极大提高了日常的开发效率。
2.1通过方法名称直接生成查询
正是因为这一便利的让人着迷的特性,让我们乐于去使用JPA这一持久化框架,接下来让我们通过几个例子去欣赏它的迷人之处吧。
2.1.1一般写法
/*** 示例1* SQL: SELECT * FROM actor WHERE first_name = ?* 参数名的定义不影响程序的运行*/List<Actor> findByFirstName(String name);/*** 示例2* SQL: SELECT * FROM actor WHERE first_name = ? AND last_name = ?* 如果明确知道查询结果返回唯一一条记录时,建议使用单一实体类作为返回类型*/List<Actor> findByFirstNameAndLastName(String name1, String name2);/*** 示例3* SQL: SELECT * FROM actor WHERE actor_id <= ?*/List<Actor>findByActorIdLessThanEqual(Integer id);
遵照JPA的规范,通过定义类似以上接口方法的形式就可以零SQL实现我们需要的单表查询(不能实现DML操作)操作。JPA对此类查询方式有很丰富的支持,受限于篇幅,我们就不一一讲述了,详细的内容可以阅读官方文档
地址:
https://docs.spring.io/spring-data/jpa/docs/2.5.6/reference/html/#repository-query-keywords
Tips
1.在查询场景中,自定义的查询接口中,find关键词(也可以是search、query、get)后面必须跟随By关键词
2.Between适用于数值、日期字段,用于日期时,参数类型可以是java.util.Date或java.sql.Timestamp
List<Film> findByLengthBetween(Integer low, Integer up);List<Film> findByLastUpdateBetween(Date startDate, Date endDate);
3.IsEmpty / IsNotEmpty只能用于集合类型的字段
4.Before或者After可用于日期、数值类型的字段
List<Film> findByLengthBefore(Integer length)
5.涉及到删除和修改时,需要在方法定义上加上@Modifying
2.2基于@Query 注解的操作
2.2.1使用JPQL
JPQL是通过Hibernate的HQL演变过来的,它和HQL语法及其相似。
/*** 示例1* SQL: SELECT * FROM actor WHERE first_name = ?*/@Query("FROM Actor WHERE firstName = ?1")List<Actor> findByFirstName(String name);/**-
* 示例2 -
* SQL: SELECT * FROM actor WHERE first_name = ? AND last_name = ? -
*/ @Query("FROM Actor WHERE firstName = ?1 AND lastName = ?2")List<Actor> findByFirstNameAndLastName(String name1, String name2);/**-
* 示例3 -
* SQL: SELECT * FROM actor WHERE actor_id <= ? -
*/ @Query("FROM Actor WHERE actorId <= ?1")List<Actor> findByActorIdLessThanEqual(Integer id);/**-
* 示例4 -
* SQL: SELECT * FROM actor -
* 不能写"SELECT *" 要写"SELECT 别名" -
*/ @Query("SELECT a FROM Actor a")List<Actor> findAll()
通过以上例子我们发现,JPQL与SQL的区别是,SQL是面向对象关系数据库,它操作的是数据表和数据列,而JPQL操作的对象是实体对象和实体属性,区分大小写,出现的SQL关键字还是原有的意思,不区分大小写。JPQL也可以支持复杂的联表查询。下面是JPQL的基本格式:👉SELECT 实体别名.属性名, 实体别名.属性名 FROM 实体名 AS 实体别名 WHERE 实体别名.实体属性 op 比较值
看完查询的写法,我们来看看DML操作的写法
@Modifying@Query(value = "UPDATE Film SET description = :description WHERE filmId = :id")void update(@Param("id") Integer filmId, @Param("description") String description);
2.2.2使用原生SQL
在Query注解中,有一个属性字段nativeQuery,默认情况下为false,即为JPQL模式,如果我们设置为true,则我们可以value属性中定义原生SQL语句实现数据库操作。
@Query("SELECT * FROM actor WHERE first_name = ?1", nativeQuery = true)List<Actor> findByFirstName(String name);@Query("SELECT * FROM actor WHERE first_name = ?1 AND last_name = ?2", nativeQuery = true)List<Actor> findByFirstNameAndLastName(String name1, String name2);@Query("SELECT * FROM actor WHERE actor_id <= ?", nativeQuery = true)List<Actor> findByActorIdLessThanEqual(Integer id);@Query("SELECT first_name, last_name FROM actor", nativeQuery = true)List<Map<String, Object>> findAll();// List<String[]> findAll();
通过上述示例,我们总结几点编写原生SQL需要注意的地方
1.如果接口方法返回的是实体对象,如List<Actor>、Actor这样的,则SELECT部分不能指定字段名,必须为*
2.如果只想查询指定列的数据,方法定义时的返回类型可以是Map<String, Object>、String[],返回数据如果是多条,则用集合类嵌套
3.关于JPA接口方法的返回类型我们可以参考官方文档,地址为:
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#repository-query-return-types
三、JPA的原理浅析
通过前面的学习,我们已经对JPA有了比较清晰的了解,不再是门外汉了,当然我们还是会有很多的疑问:
○为什么我们只是加入了一个Maven的依赖,就能直接使用JPA?
○为什么我们只是定义了一个Repository的接口就能直接使用它来实现数据库操作逻辑?
○为什么我们只是按照JPA的规范,定义了一个接口方法就能在使用时生成想要的SQL?
在接下来的学习中一一解开。
3.1JPA的自动配置
虽然这一节是要讲JPA的自动配置,其实本质要说的是Spring Boot自动加载配置的原理。在我们使用Spring Boot创建一个项目时,必不可少的一个注解就是@SpringBootApplication,看着它我们既熟悉又陌生。我们通过它的源码来一探究竟。
package org.springframework.boot.autoconfigure;@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@SpringBootConfiguration@EnableAutoConfiguration@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),-
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) public @interface SpringBootApplication {@AliasFor(annotation = EnableAutoConfiguration.class)Class<?>[] exclude() default {};@AliasFor(annotation = EnableAutoConfiguration.class)String[] excludeName() default {};@AliasFor(annotation = ComponentScan.class, attribute = "basePackages")String[] scanBasePackages() default {};@AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")Class<?>[] scanBasePackageClasses() default {};@AliasFor(annotation = ComponentScan.class, attribute = "nameGenerator")Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;@AliasFor(annotation = Configuration.class)boolean proxyBeanMethods() default true;}
通过源码我们发现其实SpringBootApplication也被一些注解所修饰,其中就有EnableAutoConfiguration注解,一看名字我们就知道它就是我们今天要找的正主。同样的,我们也不会放过它的源码的。
package org.springframework.boot.autoconfigure;@Target(ElementType.TYPE)@Retention(RetentionPolicy.RUNTIME)@Documented@Inherited@AutoConfigurationPackage@Import(AutoConfigurationImportSelector.class)public @interface EnableAutoConfiguration {-
String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration"; -
Class<?>[] exclude() default {}; -
String[] excludeName() default {}; }
我们又看到了熟悉的身影,@Import注解,它意味着在我们的IOC容器中引入一个AutoConfigurationImportSelector对象。
通过源码我们又发现AutoConfigurationImportSelector最终实现了ImportSelector接口。因此在程序的启动过程中,会执行selectImports方法,在当前的实现中,这个方法的主要逻辑是去读取一个 spring.factories下key为EnableAutoConfiguration对应的全限定名的值。
spring.factories里面配置的那些类,主要作用是告诉 Spring Boot这个stareter所需要加载哪些xxxAutoConfiguration类,也就是你真正的要自动注册的那些bean或功能。
而在SpringBoot中的META-INF/spring.factories(完整路径:spring-boot/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories)中关于EnableAutoConfiguration的这段配置如下 :

可以发现有JpaRepositoriesAutoConfiguration和HibernateJpaAutoConfiguration帮我们配置了JPA 相关的配置。至此,我们的第一个疑惑可以解开了。
3.2 SimpleJpaRepository类
接下来我们要来解决第二个疑惑,其实本节的标题已经告诉了我们答案。先上源码:
@Repository@Transactional(readOnly = true)public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T, ID> {}
我们不用看具体实现的方法逻辑了,单从SimpleJpaRepository的声明来看,它是一个类,不是抽象类是一个实现了JpaRepositoryImplementation接口的类
实际上JpaRepositoryImplementation接口也继承了JpaRepository和JpaSpecificationExecutor接口。
到此,我们可以大胆的猜测,为什么我们定义的repository接口只是继承了JpaRepository和JpaSpecificationExecutor接口就能使用一系列的接口调用,其实是SimpleJpaRepository类帮我们做了要做的事情。
那到底是不是,如果是的,那JPA是怎么做到了的呢?我们继续一步一步分析。首先我们得有一个分析的起点,上一节我们讲了JPA的自动配置,那我们是不是需要看看它到底配置了什么,看看能不能找到我们想要的。我们直接看JpaRepositoriesAutoConfiguration,因为它的名字里带Repositories。
@Configuration(proxyBeanMethods = false)@ConditionalOnBean(DataSource.class)@ConditionalOnClass(JpaRepository.class)@ConditionalOnMissingBean({ JpaRepositoryFactoryBean.class, JpaRepositoryConfigExtension.class })@ConditionalOnProperty(prefix = "spring.data.jpa.repositories", name = "enabled", havingValue = "true",-
matchIfMissing = true) @Import(JpaRepositoriesRegistrar.class)@AutoConfigureAfter({ HibernateJpaAutoConfiguration.class, TaskExecutionAutoConfiguration.class })public class JpaRepositoriesAutoConfiguration {}
我们好像运气不错,果然发现有一个Import注解引入了一个JpaRepositoriesRegistrar类,从名字看,JPA repository注册器,好像越来越接近了,直接上源码:
class JpaRepositoriesRegistrar extends AbstractRepositoryConfigurationSourceSupport {public abstract class AbstractRepositoryConfigurationSourceSupport-
implements ImportBeanDefinitionRegistrar, BeanFactoryAware, ResourceLoaderAware, EnvironmentAware { -
private ResourceLoader resourceLoader; -
private BeanFactory beanFactory; -
private Environment environment; -
@Override -
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {-
RepositoryConfigurationDelegate delegate = new RepositoryConfigurationDelegate( -
getConfigurationSource(registry, importBeanNameGenerator), this.resourceLoader, this.environment); -
delegate.registerRepositoriesIn(registry, getRepositoryConfigurationExtension()); }@Overridepublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {registerBeanDefinitions(importingClassMetadata, registry, null);}-
// 其他源码 -
... }
其实分析了这么久,如果我们经常去看看Spring系的一些源码,会发现很多老朋友。我们看到其中一个关键接口ImportBeanDefinitionRegistrar,它的关键方法就是registerBeanDefinitions了,就是按照我们的实现逻辑把一些我们需要的bean注册到IOC容器中。分析到这,我们通过测试代码调试来看看吧。

我们可以看到debug窗口中,在测试类中注入的CustomerRepository接口的实际对象是JdkDynamicAopProxy类型,这是一个jdk动态代理类,这确实比较符合我们对Spring IOC容器对接口类注入处理的认识。我们看一下它代理的目标类,发现是SimpleJpaRepository类,如此就证实了我们之前的猜测。第一次调试到此结束。
我们再回过头来看我们之前找到的AbstractRepositoryConfigurationSourceSupport类中registerBeanDefinitions方法,这个是在项目启动过程中执行的,我们加上断点进行第二次调试。
registerBeanDefinitions方法中主要的逻辑在delegate.registerRepositoriesIn中实现,我们直接在里面标记上断点。

当执行到上面断点后,我们看到configurations对象中已经有了我们自己定义的repository接口的信息了,接下来我们将进入for循环分别处理我们定义的接口了,我们进到下一个断点继续看。

在当前断点,我们可以看到,beanName是符合我们正常创建IOC容器中bean的命名规则的,但是JPA为我们创建的BeanDefinition对象是org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean类型,它是一个FactoryBean。我们继续看187、189行的代码
JPA将我们的BeanDefinition对象设置了一个name为factoryBeanObjectType,value为当前被处理的repository接口的全限定类名(比如ai.advance.jpademo.repository.FilmRepository)的attribute
最后将BeanDefinition对象注册到了我们的IOC容器中。因此当for循环执行完后,如果我们使用repository接口对应的beanName从IOC容器中获取一个实例,最开始我们拿到的是一个JpaRepositoryFactoryBean类型的Bean,当然我们最后真正拿到的bean对象是由getObject方法返回的对象。我们先来看一下它的继承关系:

JpaRepositoryFactoryBean既是一个工厂,又是一个bean。其作用类似于@Bean注解,但比其能实现更多复杂的功能,可以对对象增强。重要方法是getObject(),返回一个bean,因此我们去看一下getObject方法的实现,实际它的getObject方法的实现在其父类RepositoryFactoryBeanSupport中。
@Nonnullpublic T getObject() {return this.repository.get();}
好像没什么东西,有点懵,那我们先忽略它,我们再一想,JpaRepositoryFactoryBean对象也是一个Bean,那有没有一点和bean实例化过程相关的代码,然后我们找到了这块代码:
@Overridepublic void afterPropertiesSet() {Assert.state(entityManager != null, "EntityManager must not be null!");super.afterPropertiesSet();}
而确实JpaRepositoryFactoryBean的父类RepositoryFactoryBeanSupport也实现了InitializingBean接口,它自身也是调用了父类的afterPropertiesSet方法。
// 以下代码实现在org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport中public void afterPropertiesSet() {-
this.factory = createRepositoryFactory(); this.factory.setQueryLookupStrategyKey(queryLookupStrategyKey);-
this.factory.setNamedQueries(namedQueries); -
this.factory.setEvaluationContextProvider( -
evaluationContextProvider.orElseGet(() -> QueryMethodEvaluationContextProvider.DEFAULT)); this.factory.setBeanClassLoader(classLoader);this.factory.setBeanFactory(beanFactory);if (publisher != null) {-
this.factory.addRepositoryProxyPostProcessor(new EventPublishingRepositoryProxyPostProcessor(publisher)); }repositoryBaseClass.ifPresent(this.factory::setRepositoryBaseClass);this.repositoryFactoryCustomizers.forEach(customizer -> customizer.customize(this.factory));RepositoryFragments customImplementationFragment = customImplementation //-
.map(RepositoryFragments::just) // -
.orElseGet(RepositoryFragments::empty); RepositoryFragments repositoryFragmentsToUse = this.repositoryFragments //-
.orElseGet(RepositoryFragments::empty) // -
.append(customImplementationFragment); -
this.repositoryMetadata = this.factory.getRepositoryMetadata(repositoryInterface); this.repository = Lazy.of(() -> this.factory.getRepository(repositoryInterface, repositoryFragmentsToUse));// Make sure the aggregate root type is present in the MappingContext (e.g. for auditing)-
this.mappingContext.ifPresent(it -> it.getPersistentEntity(repositoryMetadata.getDomainType())); if (!lazyInit) {this.repository.get();-
} }
逻辑有点多,我们先看到这一行代码:
this.repository = Lazy.of(() -> this.factory.getRepository(repositoryInterface, repositoryFragmentsToUse));
先提一句Lazy类继承了java.util.function.Supplier接口。这个this.repository我们是不是在getObject方法有看到,我们还发现它的get方法返回的结果是由this.factory.getRepository()得到的,那这个this.factory又是谁呢,这时我们就要返回来看afterPropertiesSet方法的第一行代码:
- this.factory = createRepositoryFactory();
直接看createRepositoryFactory方法,它是在JpaRepositoryFactoryBean的直接父类TransactionalRepositoryFactoryBeanSupport中实现的。
protected finalRepositoryFactorySupport createRepositoryFactory() {RepositoryFactorySupport factory = doCreateRepositoryFactory();RepositoryProxyPostProcessor exceptionPostProcessor = this.exceptionPostProcessor;if (exceptionPostProcessor != null) {-
factory.addRepositoryProxyPostProcessor(exceptionPostProcessor); -
} RepositoryProxyPostProcessor txPostProcessor = this.txPostProcessor;if (txPostProcessor != null) {factory.addRepositoryProxyPostProcessor(txPostProcessor);-
} -
return factory; }
我们直接看关键代码 — doCreateRepositoryFactory方法,这个是由JpaRepositoryFactoryBean类自己实现的。
protected RepositoryFactorySupport doCreateRepositoryFactory() {Assert.state(entityManager != null, "EntityManager must not be null!");return createRepositoryFactory(entityManager);}/**-
* Returns a {@link RepositoryFactorySupport}. -
*/ protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {-
JpaRepositoryFactory jpaRepositoryFactory = new JpaRepositoryFactory(entityManager); -
jpaRepositoryFactory.setEntityPathResolver(entityPathResolver); -
jpaRepositoryFactory.setEscapeCharacter(escapeCharacter); -
if (queryMethodFactory != null) { -
jpaRepositoryFactory.setQueryMethodFactory(queryMethodFactory); }return jpaRepositoryFactory;}
我们直接看到createRepositoryFactory方法,它最后返回是一个JpaRepositoryFactory类型的对象,其他的我们就先不关心了,直接看JpaRepositoryFactory类。我们先来一张类关系图:

我们直奔我们的主题– getRepository方法,它是在JpaRepositoryFactory的父类RepositoryFactorySupport中实现的。
public <T> T getRepository(Class<T> repositoryInterface, RepositoryFragments fragments) {...RepositoryInformation information = getRepositoryInformation(metadata, composition);...Object target = getTargetRepository(information);...ProxyFactory result = new ProxyFactory();result.setTarget(target);result.setInterfaces(repositoryInterface, Repository.class, TransactionalProxy.class);...T repository = (T) result.getProxy(classLoader);...return repository;}
实现代码有点多,这里仅展示我们关注的关键代码,去掉其他多余的代码之后,有没有发现上面的逻辑很熟悉,其实就是构建一个代理类实例的过程,因此也解释了为什么我们在第一次调试看到实际注入的是一个JdkDynamicAopProxy类型的实体。那我们得好好看看代理对象的目标对象是怎么得到的,请看getTargetRepository方法,发现他需要一个RepositoryInformation类型的传参,我继续往上找,看到了getRepositoryInformation方法。
private RepositoryInformation getRepositoryInformation(RepositoryMetadata metadata,-
RepositoryComposition composition) { -
RepositoryInformationCacheKey cacheKey = new RepositoryInformationCacheKey(metadata, composition); return repositoryInformationCache.computeIfAbsent(cacheKey, key -> {Class<?> baseClass = repositoryBaseClass.orElse(getRepositoryBaseClass(metadata));return new DefaultRepositoryInformation(metadata, baseClass, composition);-
}); }
其中,可以看到一个getRepositoryBaseClass方法
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {return SimpleJpaRepository.class;}
好了,什么也不用说了,它直接给我们返回了SimpleJpaRepository的Class对象。其他的逻辑我们也不看了,虽然还有一些包装和判断的过程,但是我们今天的目的已经达到了,第二个疑惑也算比较好的解答了。最后顺便提一句,如果你的项目是手动使用了@EnableJpaRepositories注解,可能你的调试过程开局会有点不一样,但是后续的逻辑是相同的,自己可以去试试。代码如下:
@EnableJpaRepositories(basePackages = "ai.advance.jpademo.repository")@SpringBootApplicationpublic class JpaDemoApplication {public static void main(String[] args) {SpringApplication.run(JpaDemoApplication.class, args);}}
3.3自定义查询
我们还有一个疑问,为什么我们自己定义的那些不属于SimpleJpaRepository类的方法也能被调用,并且被正确生成SQL?我们又要回到上节分析的getRepository方法,同样的我们去掉不需要关心的代码。
public <T> T getRepository(Class<T> repositoryInterface, RepositoryFragments fragments) {...ProxyFactory result = new ProxyFactory();...Optional<QueryLookupStrategy> queryLookupStrategy = getQueryLookupStrategy(queryLookupStrategyKey,evaluationContextProvider);result.addAdvice(new QueryExecutorMethodInterceptor(information, projectionFactory, queryLookupStrategy,namedQueries, queryPostProcessors, methodInvocationListeners));result.addAdvice(new ImplementationMethodExecutionInterceptor(information, compositionToUse, methodInvocationListeners));T repository = (T) result.getProxy(classLoader);...return repository;}
从上面的源码我们可以看到ProxyFactory对象在最后有两次addAdvice方法的调用,目的是为了增加QueryExecutorMethodInterceptor
和ImplementationMethodExecutionInterceptor两个拦截器,它们都实现了MethodInterceptor接口,其中ImplementationMethodExecutionInterceptor为RepositoryFactorySupport的静态内部类。
3.3.1QueryExecutorMethodInterceptor
QueryExecutorMethodInterceptor这个拦截器是用来拦截处理我们在repository接口中自定义的方法的。在QueryExecutorMethodInterceptor的成员变量中有一个定义为Map<Method, RepositoryQuery>类型的queries变量,这个变量主要保存了自定义方法对象与一个RepositoryQuery对象的映射关系。

RepositoryQuery的直接抽象子类是AbstractJpaQuery,可以看到,一个RepositoryQuery实例持有一个JpaQueryMethod实例,JpaQueryMethod又持有一个Method实例,所以RepositoryQuery实例的用途很明显,一个RepositoryQuery代表了Repository接口中的一个方法,根据方法头上注解不同的形态,将每个Repository接口中的方法分别映射成相对应的RepositoryQuery实例。我们通过类关系图来熟悉一下RepositoryQuery具体的实现类有哪些。

下面我们看看JPA在哪些情况下创建对应的那个RepositoryQuery对象
1.SimpleJpaQuery方法头上@Query注解的nativeQuery属性缺省值为false,也就是使用JPQL,此时会创建SimpleJpaQuery实例,并通过两个StringQuery类实例分别持有query JPQL语句和根据query JPQL计算拼接出来的countQuery JPQL语句
2.NativeJpaQuery方法头上@Query注解的nativeQuery属性如果显式的设置为nativeQuery=true,也就是使用原生SQL的时候
3.PartTreeJpaQuery方法头上未进行@Query注解,将使用spring-data-jpa独创的方法名识别的方式进行sql语句拼接
4.NamedQuery使用javax.persistence.NamedQuery注解访问数据库的形式的时候
5.StoredProcedureJpaQuery在Repository接口的方法头上使用org.springframework.data.jpa.repository.query.Procedure注解,也就是调用存储过程的方式访问数据库的时候
所以QueryExecutorMethodInterceptor最终的目的就是根据当前需要调用的自定义的Repository的方法找到对应的RepositoryQuery对象,并构建调用信息并使用invoke方法触发调用,主要逻辑在QueryExecutorMethodInterceptor类的doInvoke方法中。
private Object doInvoke(MethodInvocation invocation) throws Throwable {Method method = invocation.getMethod();if (hasQueryFor(method)) {RepositoryMethodInvoker invocationMetadata = invocationMetadataCache.get(method);if (invocationMetadata == null) {invocationMetadata = RepositoryMethodInvoker.forRepositoryQuery(method, queries.get(method));invocationMetadataCache.put(method,invocationMetadata);}return invocationMetadata.invoke(repositoryInformation.getRepositoryInterface(), invocationMulticaster,invocation.getArguments());}return invocation.proceed();}
其实最终调用的是RepositoryMethodInvoker类中的doInvoke方法,我们打上断点来看一下

3.3.2ImplementationMethodExecutionInterceptor
ImplementationMethodExecutionInterceptor这个拦截器是来处理SimpleJpaRepository类本身实现的方法调用的。它是RepositoryFactorySupport类的静态内部类,只要没有被QueryExecutorMethodInterceptor拦截器处理的方法调用都会由它来处理,最终也是调用invoke方法。
public Object invoke(@SuppressWarnings("null") MethodInvocation invocation) throws Throwable {Method method = invocation.getMethod();Object[] arguments = invocation.getArguments();try {return composition.invoke(invocationMulticaster, method, arguments);} catch (Exception e) {org.springframework.data.repository.util.ClassUtils.unwrapReflectionException(e);}throw new IllegalStateException("Should not occur!");}
其实这里面的逻辑属于中规中矩的代理类的拦截调用。我们拿findById这个方法作为例子,打上断点看看

从上面信息可以看出来ImplementationMethodExecutionInterceptor类的invoke方法调用的是RepositoryComposition类的invoke方法,如果继续深入,其实最终也是调用的RepositoryMethodInvoker类中的doInvoke方法

至此,我们开头的几大疑惑基本都得到了解释。
四、总结
我也是尽量把⾃⼰知道的知识写明⽩,奈何本⼈⽔平有限,接触JPA也不久,如果存在纰漏欢迎指正。关于JPA更深层次的东⻄我也还在学习中,希望之后有机会再分享。
当身份认证成为开户基础设施,风控的价值重心将移向哪里
ADVANCE.AI 联合建模:让信用评估更贴近真实业务
AI Agent时代,企业将迎来第四种新身份