---
title: Spring data JPA实践和原理浅析
description: 时代在不断进步，技术也不断在发展，市面上已经有很多优秀的数据库持久化框架供我们使用，今天我将带大家来了解JPA的使用。
---

<https://blog.advance.ai/zh-cn>

# [Spring data JPA实践和原理浅析](https://blog.advance.ai/zh-cn/spring-data-jpa-0)

 作者：[Sample HubSpot User](https://blog.advance.ai/zh-cn/author/sample-hubspot-user) | Feb 23, 2024 7:37:16 AM

**作者：张葛  职位：后端工程师**

# **一、前言**

从我们进⼊编程的世界，成为程序员到现在为⽌，总有⼏个感觉神奇和激动的时刻，其中肯定包括你第⼀次程序连上数据库可以实现 CURD 功能的时候，就算那时的我们写着千遍⼀律 JDBC 模板代码也是乐此不疲。

时代在不断进步，技术也不断在发展，市面上已经有很多优秀的数据库持久化框架供我们使用，今天我将带大家来了解JPA的使用。

# **二、基本使用**

在我们现在Spring Boot横行无忌的时代，在项目中引入JPA非常简单，我们以Maven以及常用的MySQL数据库为例

在pom.xml文件添加以下依赖

1. `<parent>`
2. `<groupId>org.springframework.boot</groupId>`
3. `<artifactId>spring-boot-starter-parent</artifactId>`
4. `<version>2.5.6</version>`
5. `<relativePath/>`
6. `</parent>`
7.  
8. `<!-- jpa -->`
9. `<dependency>`
10. `<groupId>org.springframework.boot</groupId>`
11. `<artifactId>spring-boot-starter-data-jpa</artifactId>`
12. `</dependency>`
13. `<!-- mysql -->`
14. `<dependency>`
15. `<groupId>mysql</groupId>`
16. `<artifactId>mysql-connector-java</artifactId>`
17. `</dependency>`

在Spring Boot的YML文件中添加以下内容

1. `spring:`
2. `datasource:`
3. `driver-class-name:`
4. `com.mysql.cj.jdbc.Driver`
5. `url:jdbc:mysql://localhost:3306/sakila?useUnicode=true&zeroDateTimeBehavior=convertToNull&autoReconnect=true&characterEncoding=utf-8 username: root`
6. `password: root`

定义Entity类，我测试过程使用的数据源为MySQL官方提供的样例数据库sakila，大家可以在https://dev.mysql.com/doc/index-other.html自行下载

1. `@Entity`
2. `@Data // lombok注解`
3. `public class Actor {`
4. ``
5. ` @Id`
6. ` // Column注解不是必须的，如果满足字段驼峰形式`
7.  `// 与数据库字段以下划线分隔形式对应即可`
8.  `@Column(name = "actor_id", nullable = false)`
9.  `private Integer actorId;`
10.  
11. ` @Column(name = "first_name", nullable = false, length = 45)`
12. ` private String firstName;`
13. ` private String lastName;`
14. ` private Timestamp lastUpdate;`
15. `}`

定义Respository接口，一般我们通过继承JpaRepository接口即能满足我们一般的CURD操作，如果需要支持复杂逻辑查询

比如：动态SQL；联表查询，则需要继承 JpaSpecificationExecutor 接口，并配合Specification的接口方法。

1. `@Repository`
2. `public interface ActorRepository extends JpaRepository<Actor, Integer> {`
3. `}`

在JpaRepository的中我们可以看到有findAll、getById、findById、save、saveAll、deleteById...这些默认定义，这是JPA为我们提供的常用的一些数据操作，我们可以直接使用，极大提高了日常的开发效率。

## 2.1通过方法名称直接生成查询

正是因为这一便利的让人着迷的特性，让我们乐于去使用JPA这一持久化框架，接下来让我们通过几个例子去欣赏它的迷人之处吧。

### **2.1.1一般写法**

1. `/**`
2. ` * 示例1`
3. ` * SQL: SELECT * FROM actor WHERE first_name = ?`
4. ` * 参数名的定义不影响程序的运行`
5. ` */`
6. `List<Actor> findByFirstName(String name);`
7.  
8. `/**`
9. ` * 示例2`
10. ` * SQL: SELECT * FROM actor WHERE first_name = ? AND last_name = ?`
11. ` * 如果明确知道查询结果返回唯一一条记录时，建议使用单一实体类作为返回类型`
12. ` */`
13. `List<Actor> findByFirstNameAndLastName(String name1, String name2);`
14.  
15. `/**`
16. ` * 示例3`
17. ` * SQL: SELECT * FROM actor WHERE actor_id <= ?`
18. ` */`
19. `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](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. `/**```
2. ` * 示例1```
3. ` * SQL: SELECT * FROM actor WHERE first_name = ?```
4. ` */`
5. `@Query("FROM Actor WHERE firstName = ?1")`
6. `List<Actor> findByFirstName(String name);```
7.  
8. `/**```
9.  `* 示例2```
10.  `* SQL: SELECT * FROM actor WHERE first_name = ? AND last_name = ?```
11.  `*/`
12. `@Query("FROM Actor WHERE firstName = ?1 AND lastName = ?2")`
13. `List<Actor> findByFirstNameAndLastName(String name1, String name2);`
14. ` `
15. `/**```
16.  `* 示例3```
17.  `* SQL: SELECT * FROM actor WHERE actor_id <= ?```
18.  `*/`
19. `@Query("FROM Actor WHERE actorId <= ?1")`
20. `List<Actor> findByActorIdLessThanEqual(Integer id);```
21.  
22. `/**```
23.  `* 示例4```
24.  `* SQL: SELECT * FROM actor```
25.  `* 不能写"SELECT *" 要写"SELECT 别名"```
26.  `*/`
27. `@Query("SELECT a FROM Actor a")`
28. `List<Actor> findAll()`

通过以上例子我们发现，JPQL与SQL的区别是，SQL是面向对象关系数据库，它操作的是数据表和数据列，而JPQL操作的对象是实体对象和实体属性，区分大小写，出现的SQL关键字还是原有的意思，不区分大小写。JPQL也可以支持复杂的联表查询。下面是JPQL的基本格式：

*👉SELECT 实体别名.属性名, 实体别名.属性名 FROM 实体名 AS 实体别名 WHERE 实体别名.实体属性 op 比较值*

看完查询的写法，我们来看看DML操作的写法

1. `@Modifying`
2. `@Query(value = "UPDATE Film SET description = :description WHERE filmId = :id")`
3. `void update(@Param("id") Integer filmId, @Param("description") String description);`

### **2.2.2使用原生SQL**

在Query注解中，有一个属性字段nativeQuery，默认情况下为false，即为JPQL模式，如果我们设置为true，则我们可以value属性中定义原生SQL语句实现数据库操作。

1. `@Query("SELECT * FROM actor WHERE first_name = ?1", nativeQuery = true)`
2. `List<Actor> findByFirstName(String name);```
3.  
4. `@Query("SELECT * FROM actor WHERE first_name = ?1 AND last_name = ?2", nativeQuery = true)`
5. `List<Actor> findByFirstNameAndLastName(String name1, String name2);```
6.  
7. `@Query("SELECT * FROM actor WHERE actor_id <= ?", nativeQuery = true)`
8. `List<Actor> findByActorIdLessThanEqual(Integer id);`
9. ` `
10. `@Query("SELECT first_name, last_name FROM actor", nativeQuery = true)`
11. `List<Map<String, Object>> findAll();`
12. `// 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](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，看着它我们既熟悉又陌生。我们通过它的源码来一探究竟。

1. `package org.springframework.boot.autoconfigure;```
2.  
3. `@Target(ElementType.TYPE)`
4. `@Retention(RetentionPolicy.RUNTIME)`
5. `@Documented`
6. `@Inherited`
7. `@SpringBootConfiguration`
8. `@EnableAutoConfiguration`
9. `@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),```
10.  `@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })`
11. `public @interface SpringBootApplication {`
12. ````
13. ` @AliasFor(annotation = EnableAutoConfiguration.class)```
14. ` Class<?>[] exclude() default {};`````
15.  
16. ` @AliasFor(annotation = EnableAutoConfiguration.class)```
17. ` String[] excludeName() default {};`````
18.  
19. ` @AliasFor(annotation = ComponentScan.class, attribute = "basePackages")```
20. ` String[] scanBasePackages() default {};`````
21.  
22. ` @AliasFor(annotation = ComponentScan.class, attribute = "basePackageClasses")```
23. ` Class<?>[] scanBasePackageClasses() default {};`````
24.  
25. ` @AliasFor(annotation = ComponentScan.class, attribute = "nameGenerator")```
26. ` Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;`````
27.  
28. ` @AliasFor(annotation = Configuration.class)```
29. ` boolean proxyBeanMethods() default true;```
30.  
31. `}`

 

通过源码我们发现其实SpringBootApplication也被一些注解所修饰，其中就有EnableAutoConfiguration注解，一看名字我们就知道它就是我们今天要找的正主。同样的，我们也不会放过它的源码的。

 

1. `package org.springframework.boot.autoconfigure;```
2.  
3. `@Target(ElementType.TYPE)`
4. `@Retention(RetentionPolicy.RUNTIME)`
5. `@Documented`
6. `@Inherited`
7. `@AutoConfigurationPackage`
8. `@Import(AutoConfigurationImportSelector.class)`
9. `public @interface EnableAutoConfiguration {`````
10.  
11.  `String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";`
12. ````
13.  `Class<?>[] exclude() default {};`
14. ````
15.  `String[] excludeName() default {};`
16. ` `
17. `}`

我们又看到了熟悉的身影，@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类**

接下来我们要来解决第二个疑惑，其实本节的标题已经告诉了我们答案。先上源码：

1. `@Repository`
2. `@Transactional(readOnly = true)`
3. `public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T, ID> {`
4. `}`

 

我们不用看具体实现的方法逻辑了，单从SimpleJpaRepository的声明来看，它是一个类，不是抽象类是一个实现了JpaRepositoryImplementation接口的类

实际上JpaRepositoryImplementation接口也继承了JpaRepository和JpaSpecificationExecutor接口。

到此，我们可以大胆的猜测，为什么我们定义的repository接口只是继承了JpaRepository和JpaSpecificationExecutor接口就能使用一系列的接口调用，其实是SimpleJpaRepository类帮我们做了要做的事情。

那到底是不是，如果是的，那JPA是怎么做到了的呢？我们继续一步一步分析。首先我们得有一个分析的起点，上一节我们讲了JPA的自动配置，那我们是不是需要看看它到底配置了什么，看看能不能找到我们想要的。我们直接看JpaRepositoriesAutoConfiguration，因为它的名字里带Repositories。

 

1. `@Configuration(proxyBeanMethods = false)`
2. `@ConditionalOnBean(DataSource.class)`
3. `@ConditionalOnClass(JpaRepository.class)`
4. `@ConditionalOnMissingBean({ JpaRepositoryFactoryBean.class, JpaRepositoryConfigExtension.class })`
5. `@ConditionalOnProperty(prefix = "spring.data.jpa.repositories", name = "enabled", havingValue = "true",```
6.  `matchIfMissing = true)`
7. `@Import(JpaRepositoriesRegistrar.class)`
8. `@AutoConfigureAfter({ HibernateJpaAutoConfiguration.class, TaskExecutionAutoConfiguration.class })`
9. `public class JpaRepositoriesAutoConfiguration {`
10. `}`

我们好像运气不错，果然发现有一个Import注解引入了一个JpaRepositoriesRegistrar类，从名字看，JPA repository注册器，好像越来越接近了，直接上源码：

1. `class JpaRepositoriesRegistrar extends AbstractRepositoryConfigurationSourceSupport {`
2. `}```
3.  
4. `public abstract class AbstractRepositoryConfigurationSourceSupport```
5.  `implements ImportBeanDefinitionRegistrar, BeanFactoryAware, ResourceLoaderAware, EnvironmentAware {`````
6.  
7.  `private ResourceLoader resourceLoader;`````
8.  
9.  `private BeanFactory beanFactory;`````
10.  
11.  `private Environment environment;```
12.  
13.  `@Override```
14.  `public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry,```
15. ` BeanNameGenerator importBeanNameGenerator) {```
16.  `RepositoryConfigurationDelegate delegate = new RepositoryConfigurationDelegate(```
17.  `getConfigurationSource(registry, importBeanNameGenerator), this.resourceLoader, this.environment);```
18.  `delegate.registerRepositoriesIn(registry, getRepositoryConfigurationExtension());```
19. `}`
20. ````
21. `@Override```
22. `public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {```
23. `registerBeanDefinitions(importingClassMetadata, registry, null);```
24. `}`
25. ````
26.  `// 其他源码```
27.  `...`
28. `}`

其实分析了这么久，如果我们经常去看看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中。

1. `@Nonnull`
2. `public T getObject() {`
3. `return this.repository.get();`
4. `}`

好像没什么东西，有点懵，那我们先忽略它，我们再一想，JpaRepositoryFactoryBean对象也是一个Bean，那有没有一点和bean实例化过程相关的代码，然后我们找到了这块代码：

1. `@Override`
2. `public void afterPropertiesSet() {`
3. ```Assert.state(entityManager != null, "EntityManager must not be null!");`````
4.  
5. `super.afterPropertiesSet();`
6. `}`

而确实JpaRepositoryFactoryBean的父类RepositoryFactoryBeanSupport也实现了InitializingBean接口，它自身也是调用了父类的afterPropertiesSet方法。

1. `// 以下代码实现在org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport中`
2. `public void afterPropertiesSet() {`
3. ````
4.   `this.factory = createRepositoryFactory();```
5. ` this.factory.setQueryLookupStrategyKey(queryLookupStrategyKey);```
6.  `this.factory.setNamedQueries(namedQueries);```
7.  `this.factory.setEvaluationContextProvider(```
8.  `evaluationContextProvider.orElseGet(() -> QueryMethodEvaluationContextProvider.DEFAULT));```
9. ` this.factory.setBeanClassLoader(classLoader);```
10. ` this.factory.setBeanFactory(beanFactory);`````
11.  
12. ` if (publisher != null) {```
13.  `this.factory.addRepositoryProxyPostProcessor(new EventPublishingRepositoryProxyPostProcessor(publisher));```
14. `}`
15. ````
16. `repositoryBaseClass.ifPresent(this.factory::setRepositoryBaseClass);`````
17.  
18. ` this.repositoryFactoryCustomizers.forEach(customizer -> customizer.customize(this.factory));`````
19.  
20. ` RepositoryFragments customImplementationFragment = customImplementation //```
21.  `.map(RepositoryFragments::just) //```
22.  `.orElseGet(RepositoryFragments::empty);`````
23.  
24. ` RepositoryFragments repositoryFragmentsToUse = this.repositoryFragments //```
25.  `.orElseGet(RepositoryFragments::empty) //```
26.  `.append(customImplementationFragment);`````
27.  
28.  `this.repositoryMetadata = this.factory.getRepositoryMetadata(repositoryInterface);`````
29.  
30. `this.repository = Lazy.of(() -> this.factory.getRepository(repositoryInterface, repositoryFragmentsToUse));`````
31.  
32. ` // Make sure the aggregate root type is present in the MappingContext (e.g. for auditing)```
33.  `this.mappingContext.ifPresent(it -> it.getPersistentEntity(repositoryMetadata.getDomainType()));`````
34.  
35. `if (!lazyInit) {```
36. `this.repository.get();```
37.  `}`
38. `}`

逻辑有点多，我们先看到这一行代码：

1. `this.repository = Lazy.of(() -> this.factory.getRepository(repositoryInterface, repositoryFragmentsToUse));`

先提一句Lazy类继承了java.util.function.Supplier接口。这个this.repository我们是不是在getObject方法有看到，我们还发现它的get方法返回的结果是由this.factory.getRepository()得到的，那这个this.factory又是谁呢，这时我们就要返回来看afterPropertiesSet方法的第一行代码：

1. this.factory = createRepositoryFactory();

直接看createRepositoryFactory方法，它是在JpaRepositoryFactoryBean的直接父类TransactionalRepositoryFactoryBeanSupport中实现的。

1. `protected final``RepositoryFactorySupport createRepositoryFactory() {`````
2.  
3. ` RepositoryFactorySupport factory = doCreateRepositoryFactory();`````
4.  
5. ` RepositoryProxyPostProcessor exceptionPostProcessor = this.exceptionPostProcessor;`````
6.  
7. ` if (exceptionPostProcessor != null) {```
8.  `factory.addRepositoryProxyPostProcessor(exceptionPostProcessor);```
9.  `}`
10. ````
11. ` RepositoryProxyPostProcessor txPostProcessor = this.txPostProcessor;`````
12.  
13. ` if (txPostProcessor != null) {```
14. ` factory.addRepositoryProxyPostProcessor(txPostProcessor);```
15.  `}`
16. ````
17.  `return factory;`
18. `}`

我们直接看关键代码 -- doCreateRepositoryFactory方法，这个是由JpaRepositoryFactoryBean类自己实现的。

1. `protected RepositoryFactorySupport doCreateRepositoryFactory() {`````
2.  
3. `Assert.state(entityManager != null, "EntityManager must not be null!");`````
4.  
5. `return createRepositoryFactory(entityManager);`
6. `}```
7.  
8. `/**```
9.  `* Returns a {@link RepositoryFactorySupport}.```
10.  `*/`
11. `protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) {`````
12.  
13.  `JpaRepositoryFactory jpaRepositoryFactory = new JpaRepositoryFactory(entityManager);```
14.  `jpaRepositoryFactory.setEntityPathResolver(entityPathResolver);```
15.  `jpaRepositoryFactory.setEscapeCharacter(escapeCharacter);`````
16.  
17.  `if (queryMethodFactory != null) {```
18.  `jpaRepositoryFactory.setQueryMethodFactory(queryMethodFactory);```
19. ` }`
20. ````
21. ` return jpaRepositoryFactory;`
22. `}`

我们直接看到createRepositoryFactory方法，它最后返回是一个JpaRepositoryFactory类型的对象，其他的我们就先不关心了，直接看JpaRepositoryFactory类。我们先来一张类关系图：

我们直奔我们的主题-- getRepository方法，它是在JpaRepositoryFactory的父类RepositoryFactorySupport中实现的。

1. `public <T> T getRepository(Class<T> repositoryInterface, RepositoryFragments fragments) {`````
2.  
3. `...`````
4.  
5. `RepositoryInformation information = getRepositoryInformation(metadata, composition);`````
6.  
7. `...`````
8.  
9. `Object target = getTargetRepository(information);`````
10.  
11. `...`````
12.  
13. `ProxyFactory result = new ProxyFactory();```
14. `result.setTarget(target);```
15. `result.setInterfaces(repositoryInterface, Repository.class, TransactionalProxy.class);`````
16.  
17. `...`````
18.  
19. `T repository = (T) result.getProxy(classLoader);`````
20.  
21. `...`
22. ````
23. `return repository;`
24. `}`

实现代码有点多，这里仅展示我们关注的关键代码，去掉其他多余的代码之后，有没有发现上面的逻辑很熟悉，其实就是构建一个代理类实例的过程，因此也解释了为什么我们在第一次调试看到实际注入的是一个JdkDynamicAopProxy类型的实体。那我们得好好看看代理对象的目标对象是怎么得到的，请看getTargetRepository方法，发现他需要一个RepositoryInformation类型的传参，我继续往上找，看到了getRepositoryInformation方法。

1. `private RepositoryInformation getRepositoryInformation(RepositoryMetadata metadata,```
2.  `RepositoryComposition composition) {`````
3.  
4.  `RepositoryInformationCacheKey cacheKey = new RepositoryInformationCacheKey(metadata, composition);`````
5.  
6. `return repositoryInformationCache.computeIfAbsent(cacheKey, key -> {`
7.  
8. `Class<?> baseClass = repositoryBaseClass.orElse(getRepositoryBaseClass(metadata));`````
9.  
10. `return new DefaultRepositoryInformation(metadata, baseClass, composition);```
11.  `});`
12. `}`

其中，可以看到一个getRepositoryBaseClass方法

1. `protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {```
2. `return SimpleJpaRepository.class;`
3. `}`

好了，什么也不用说了，它直接给我们返回了SimpleJpaRepository的Class对象。其他的逻辑我们也不看了，虽然还有一些包装和判断的过程，但是我们今天的目的已经达到了，第二个疑惑也算比较好的解答了。最后顺便提一句，如果你的项目是手动使用了@EnableJpaRepositories注解，可能你的调试过程开局会有点不一样，但是后续的逻辑是相同的，自己可以去试试。代码如下：

1. `@EnableJpaRepositories(basePackages = "ai.advance.jpademo.repository")`
2. `@SpringBootApplication`
3. `public class JpaDemoApplication {`
4. ````
5. `public static void main(String[] args) {```
6. `SpringApplication.run(JpaDemoApplication.class, args);```
7. `}`
8. ` `
9. `}`

## **3.3自定义查询**

我们还有一个疑问，为什么我们自己定义的那些不属于SimpleJpaRepository类的方法也能被调用，并且被正确生成SQL？我们又要回到上节分析的getRepository方法,同样的我们去掉不需要关心的代码。

1. `public <T> T getRepository(Class<T> repositoryInterface, RepositoryFragments fragments) {`````
2.  
3. `...`````
4.  
5. `ProxyFactory result = new ProxyFactory();`````
6.  
7. `...`````
8.  
9. `Optional<QueryLookupStrategy> queryLookupStrategy = getQueryLookupStrategy(queryLookupStrategyKey,```
10. `evaluationContextProvider);```
11. `result.addAdvice(new QueryExecutorMethodInterceptor(information, projectionFactory, queryLookupStrategy,```
12. `namedQueries, queryPostProcessors, methodInvocationListeners));`````
13.  
14. `result.addAdvice(```
15. `new ImplementationMethodExecutionInterceptor(information, compositionToUse, methodInvocationListeners));`
16.  
17. `T repository = (T) result.getProxy(classLoader);`````
18.  
19. `...`````
20.  
21. `return repository;`
22. `}`

从上面的源码我们可以看到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方法中。

1. `private Object doInvoke(MethodInvocation invocation) throws Throwable {`````
2.  
3. `Method method = invocation.getMethod();`````
4.  
5. `if (hasQueryFor(method)) {`````
6.  
7. `RepositoryMethodInvoker invocationMetadata = invocationMetadataCache.get(method);`````
8.  
9. `if (invocationMetadata == null) {``invocationMetadata = RepositoryMethodInvoker.forRepositoryQuery(method, queries.get(method));```
10. `invocationMetadataCache.put(method,`
11. `invocationMetadata);```
12. `}`````
13.  
14. `return invocationMetadata.invoke(repositoryInformation.getRepositoryInterface(), invocationMulticaster,```
15. `invocation.getArguments());```
16. `}`
17. ````
18. `return invocation.proceed();`
19. `}`

其实最终调用的是RepositoryMethodInvoker类中的doInvoke方法，我们打上断点来看一下

### **3.3.2ImplementationMethodExecutionInterceptor**

ImplementationMethodExecutionInterceptor这个拦截器是来处理SimpleJpaRepository类本身实现的方法调用的。它是RepositoryFactorySupport类的静态内部类，只要没有被QueryExecutorMethodInterceptor拦截器处理的方法调用都会由它来处理，最终也是调用invoke方法。

1. `public Object invoke(@SuppressWarnings("null") MethodInvocation invocation) throws Throwable {`````
2.  
3. `Method method = invocation.getMethod();```
4. `Object[] arguments = invocation.getArguments();`````
5.  
6. `try {```
7. `return composition.invoke(invocationMulticaster, method, arguments);```
8. `} catch (Exception e) {```
9. `org.springframework.data.repository.util.ClassUtils.unwrapReflectionException(e);```
10. `}`
11. ````
12. `thro``w new IllegalStateException("Should not occur!");`
13. `}`

其实这里面的逻辑属于中规中矩的代理类的拦截调用。我们拿findById这个方法作为例子，打上断点看看

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

至此，我们开头的几大疑惑基本都得到了解释。

 

**四、总结**

我也是尽量把⾃⼰知道的知识写明⽩，奈何本⼈⽔平有限，接触JPA也不久，如果存在纰漏欢迎指正。关于JPA更深层次的东⻄我也还在学习中，希望之后有机会再分享。

 

[查看完整帖子](https://blog.advance.ai/zh-cn/spring-data-jpa-0)

```json
{
  "@context" : "http://schema.org",
  "@type" : "BlogPosting",
  "author" : {
    "@type" : "Person",
    "name" : "Sample HubSpot User"
  },
  "dateModified" : "2024-02-26T06:09:38.533Z",
  "datePublished" : "2024-02-23T07:37:16Z",
  "headline" : "Spring data JPA实践和原理浅析",
  "image" : {
    "@type" : "ImageObject",
    "height" : 1166,
    "url" : "https://5877117.fs1.hubspotusercontent-na1.net/hubfs/5877117/%E6%88%AA%E5%B1%8F2024-02-26%2014.01.58.png",
    "width" : 2786
  },
  "mainEntityOfPage" : "https://blog.advance.ai/zh-cn/spring-data-jpa-0",
  "publisher" : {
    "@type" : "Organization",
    "logo" : {
      "@type" : "ImageObject",
      "height" : 60,
      "url" : "/hs/hsstatic/content_shared_assets/static-1.4092/img/default-amp-logo.png",
      "width" : 60
    },
    "name" : "Chinese Blog"
  }
}
```