什么是Spring Data JPA?
要解释这个问题,我们先将Spring Data JPA拆成两个部分,即Sping Data和JPA。
从这两个部分来解释。
Spring Data是什么?
摘自: https://spring.io/projects/spring-data
Spring Data’s mission is to provide a familiar and consistent, Spring-based programming model for data access while still retaining the special traits of the underlying data store.
It makes it easy to use data access technologies, relational and non-relational databases, map-reduce frameworks, and cloud-based data services. This is an umbrella project which contains many subprojects that are specific to a given database. The projects are developed by working together with many of the companies and developers that are behind these exciting technologies.
翻译:
Spring Data的使命/任务就是提供一个通用的/熟悉的和一致的,基于Spring的数据访问编程模型,同时仍然保留了底层数据存储的特性.
它使使用数据访问技术、关系和非关系数据库、map-reduce框架和基于云的数据服务变得容易。这是一个总的项目,包含许多特定于给定数据库的子项目。这些项目是通过与这些令人兴奋的技术背后的许多公司和开发人员合作开发的。
JPA是什么?
JPA全称:Java Persistence API
是一个基于ORM(对象关系映射)的标准规范,既然是规范,那自然不是具体的实现.
Hibernate就是其中的一个实现
JPA与JDBC的关系
JPA是Java持久化的API, JDBC是Java数据库连接.
JPA是JDBC上层的抽象,提供一种ORM即对象关系映射的方式来操作数据库。
JPA是基于JDBC的,就是说JPA的各种实现包括Hibernate,JPA是基于JDBC的更高级的数据库访问技术。
JDBC是java语言中用于连接和操作数据库的API,提供了一套标准接口和方法。然后基于各个数据库的的驱动来与数据库进行交互。
以一个简单的方法作为切入口了解一下Spring Data Jpa的源码
这里是基于SpringBoot 2.3.12.RELEASE版本来研究源码的。
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-jpaartifactId>
dependency>
所以在maven的pom.xml中引用jpa.
根据spring-boot-autoconfigure的jar包中会有spring-autoconfigure-metadata.properties文件,
这里面配置了匹配jpa自动配置的条件。如下
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration=
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration.AutoConfigureAfter=org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration,org.springframework.boot.autoconfigure.task.TaskExecutionAutoConfiguration
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration.ConditionalOnBean=javax.sql.DataSource
org.springframework.boot.autoconfigure.data.jpa.JpaRepositoriesAutoConfiguration.ConditionalOnClass=org.springframework.data.jpa.repository.JpaRepository
通过上述配置可知:要有数据源,要有JpaRepository以及HibernateJpaAutoConfiguration等
因为JPA是基于Hibernate来的,所以需要先有Hibernate所以也就必须先进行
HibernateJpaAutoConfiguration自动配置
整体分析源码的流程是,围绕三个问题展开的,问题如下:
1.为什么我自己定义的AccountRepository
接口的类型为SimpleJpaRepository
,这是从哪里来的?
2.代理SimpleJpaRepositiry
是在什么时候创建的执行的?
3.为什么根据accountRepository
获取对象获取到的是JpaRepositoryFactoryBean
这个类型的对象?
关于Spring Data Jpa的源码, 我们可以通过一个简单的例子来作为切入口,例子如下
// 写一个单元测试的类
@RunWith(SpringRunner.class)
@SpringBootTest
public class AccountRepositoryTest {
@Autowired
private AccountRepository accountRepository;
@Test
public void test() {
Optional<Account> accountOptional = accountRepository.findById(1L);
if (accountOptional.isPresent()) {
Account account = accountOptional.get();
System.out.println(account);
}
}
}
accountRepository
是一个SimpleJapRepository
类型的代理对象
至此我们需要搞明白两个事情:1、为什么是SimpleJpaRepository
,这是从哪里来的。2、代理对象是什么时候创建的
带着这两个问题我们去看源码。
熟悉Spring生命周期的应该知道AccountRepository
接口上的@Repository
注解会Spring扫描处理,并实例化。定位到具体代码
// AbstractApplicationContext类的refresh() --> finishBeanFactoryInitialization(beanFactory)
// Instantiate all remaining (non-lazy-init) singletons.
beanFactory.preInstantiateSingletons();
可以看到在这一步accountRepository
已经是一个FactoryBean
,所以if
的这个分支会进入,在这里会对FactoryBean
的对象调用getBean(String name)
方法,从而进入Spring创建对象的生命周期中。
再看这个循环里面的beanNames
来自于beanDefinitionNames
即是存放beanDefinition
对象集合的名称集合。进入isFactoryBean(String name)
方法内容如下:
发现获取到的单例对象类型为JpaRepositoryFactoryBean
,这里又增加了一个新的问题,即3、为什么根据accountRepository
获取对象获取到的是JpaRepositoryFactoryBean
这个类型的对象。
由于当代码执行到这一步的时候,说明BeanDifinition
集合已经存在了,即Spring已经将类构造成BeanDifinition
对象放入集合中,后续会根据BeanDifinition
集合来创建对象。所以我们需要找到初始化BeanDifinition
对象的地方。
根据beanDefinitionNames
找到调用的添加操作的地方
找到了registerBeanDefinition
方法,但是这个时候BeanDefinition
已经是JapRepositoryFactoryBean
了,还得往前找。
RepositoryConfigurationDelegate
类的registerRepositoriesIn
方法中调用了上面的registerBeanDefinition
方法,该方法将AccountRepository
在BeanDefinition
中注册为JpaRepositoryFactoryBean
先看builder.build(configuration)
方法,在build
方法中会获取到JapRepositoryFactoryBean
类型并注册
build
方法执行结束后,会使用返回的definitionBuilder
获取beanDefinition
对象,并将beanDefinition
传入registerBeanDefinition
方法
到这里就解释了,为什么accountRepository
对应的BeanDefinition
对象类型为JapRepositoryFactoryBean
类型。在Spring中FactoryBean
的作用就是生产某一种类型的对象,核心方法为T getObject() throws Exception;
至此解释了一个问题:为什么根据accountRepository
获取对象获取到的是JpaRepositoryFactoryBean
这个类型的对象。
JpaRepositoryFactoryBean
的getObject()
方法接下来重点看下JpaRepositoryFactoryBean
的getObject()
方法
这里方法很简单,直接从this.respository
中获取对象即可,但是this.repository
是何时初始化的呢?查看JpaRepositoryFactoryBean
的类图,发现实现了InitializingBean
接口
getObject()
方法中的this.repository
的初始化找到InitializingBean
接口的实现方法,发现在afterPropertiesSet()
方法中对this.respository
进行初始化了。
SimpleJpaRepository
的由来进入getRepository(Class
方法进一步查看代码
再看这个方法getRepositoryInformation(metadata, composition) --> getRepositoryBaseClass(metadata)
在初始化this.repository
的时候,就固定将repository
固定设置为SimpleJpaRepository
类型了。
到这里就解释了为什么是SimpleJpaRepository
类型了,回答了最开始提出的第一个问题。
AccountRepository
代理对象的创建此时回到RepositoryFactorySupport.getRepository(Class
方法
可以从中看到当设置了repositoryBaseClass
为SimpleJpaRepository
类型之后,开始通过动态代理的方式来创建对象了。
在Object target = getTargetRepository(information);
中通过反射来创建SimpleJpaRepository
对象,SimpleJpaRepository
是实现了JpaRepository
接口的,与我们自己定义的AccountRepository
接口一样都实现了JpaRepository
即可,这满足了JDK动态代理的条件,即需要代理的对象与代理对象都实现了相同的接口。
接下来使用ProxyFactory
来创建代理对象的,从最开始的结果可以知道使用的是JDK的动态代理,所以需要传递需要代理的接口,
设置需要代理的接口,和实现类。通过最后的T repository = (T) result.getProxy(classLoader);
来创建并获取代理对象。
/**
* Create a new proxy according to the settings in this factory.
* Can be called repeatedly. Effect will vary if we've added
* or removed interfaces. Can add and remove interceptors.
*
Uses the given class loader (if necessary for proxy creation).
* @param classLoader the class loader to create the proxy with
* (or {@code null} for the low-level proxy facility's default)
* @return the proxy object
*/
public Object getProxy(@Nullable ClassLoader classLoader) {
return createAopProxy().getProxy(classLoader);
}
最终得到了accountRepository
的代理对象,然后调用findById(ID id)
@Override
public Optional<T> findById(ID id) {
Assert.notNull(id, ID_MUST_NOT_BE_NULL);
Class<T> domainType = getDomainClass();
if (metadata == null) {
return Optional.ofNullable(em.find(domainType, id));
}
LockModeType type = metadata.getLockModeType();
Map<String, Object> hints = getQueryHints().withFetchGraphs(em).asMap();
// 最终调用SessionImpl.find()方法查询返回结果
return Optional.ofNullable(type == null ? em.find(domainType, id, hints) : em.find(domainType, id, type, hints));
}