前言

不知道从啥时候开始项目上就一直用MyBatis,其实我个人更新JPA些,因为JPA看起来OO的思想更强烈些,所以这才最近把JPA拿出来再看一看,使用起来也很简单,除了定义Entity实体外,声明自己的业务接口继承JpaRepository接口,什么逻辑也不用写,基本的增删改查,分页,排序就都搞定了。

我在实现JpaRepository接口时就有个疑问,那么实现类是什么?如果用过MyBatis肯定也知道,是接口和实现类之间有一个代理类专门来处理这块的业务,那么JPA这块是否也会有一个代理类来处理同样的业务呢? 总体来说我们有两个疑问,关键字分别是:接口实现类,代理类是什么

工作原理分析

首先从spring-boot-autoconfiguration.jar中下的spring.factories中我们可以看到JPA的自动配置需要从JpaRepositoriesAutoConfiguration开始着手。 我先画了一张总的Spring Data JPA自动配置流程图,可以有个大概的认识,下面会从源代码层面再来读一读其工作原理,和关键代码都分布在那里。

JpaRepositoriesAutoConfiguration 自动配置

因为我们在pom中导入了spring-data-jpa.jar,数据库驱动jar包为系统默认jar,也就是说他们会出现在程序运行的classpath上,并且我们在yml文件中配置了数据源,所以在springboot程序启动中,springboot自动配置中关于JPA的自动配置就已经开始工作了,具体的自动配置类会从JpaRepositoriesAutoConfiguration开始。

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3307/readinglist?serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval\
=true
spring.datasource.username=root
spring.datasource.password=000
spring.jpa.generate-ddl=false
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true

从代码中可以看到JPA的默认实现是Hibernate,所以会先配置HibernateJpaAutoConfiguration,并且是在DataSource bean已经存在的情况下。

@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)
//导入JpaRepositoriesRegistrar
@Import(JpaRepositoriesRegistrar.class)
//先自动配置HibernateJpaAutoConfiguration, TaskExecutionAutoConfiguration
@AutoConfigureAfter({ HibernateJpaAutoConfiguration.class, TaskExecutionAutoConfiguration.class })
public class JpaRepositoriesAutoConfiguration {
}
@Configuration(proxyBeanMethods = false)
@ConditionalOnClass({ LocalContainerEntityManagerFactoryBean.class, EntityManager.class, SessionImplementor.class })
@EnableConfigurationProperties(JpaProperties.class)
@AutoConfigureAfter({ DataSourceAutoConfiguration.class })
@Import(HibernateJpaConfiguration.class)
public class HibernateJpaAutoConfiguration { }

这里你首先会看到必须是DataSource bean存在的情况下,其次还有一个关键信息就是不存在JpaRepositoryFactoryBean bean的情况下才会执行该自动配置,也就是说如果你想根据自己的业务重新实现一个FactoryBean,那么该自动配置则不会执行。 那么看起来JpaRepositoryFactoryBean可能看起来有点眼熟哦。

JpaRepositoryFactoryBean位于org.springframework.data.jpa.repository.support包下

用户自定义的JpaRepository作为bean注入Spring容器中

JpaRepositoriesRegistrar中静态内部类使用了@EnableJpaRepositories开启JPA。如果不是SpringBoot项目中该注解是需要手动开启。

class JpaRepositoriesRegistrar extends AbstractRepositoryConfigurationSourceSupport {

	@EnableJpaRepositories
private static class EnableJpaRepositoriesConfiguration { } }

JpaRepositoriesRegistrar又继承了抽象类AbstractRepositoryConfigurationSourceSupport类。这是一个ImportBeanDefinitionRegistrar,设计目的就是在SpringBoot自动发现机制中发现用户自定义的JpaRepository。在Spring容器启动中,该ImportBeanDefinitionRegistrar就会执行。

public abstract class AbstractRepositoryConfigurationSourceSupport
implements ImportBeanDefinitionRegistrar, BeanFactoryAware, ResourceLoaderAware, EnvironmentAware {
}
  在AbstractRepositoryConfigurationSourceSupport类中重写了registerBeanDefinitions方法,这个方法里又把实例化的任务交给了RepositoryConfigurationDelegate#registerRepositoriesIn()。

AbstractRepositoryConfigurationSourceSupport#registerBeanDefinitions

@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());
} @Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
registerBeanDefinitions(importingClassMetadata, registry, null);
}

RepositoryConfigurationDelegate#registerRepositoriesIn

public class RepositoryConfigurationDelegate {

    public List<BeanComponentDefinition> registerRepositoriesIn(BeanDefinitionRegistry registry, RepositoryConfigurationExtension extension) {

        extension.registerBeansForRoot(registry, this.configurationSource);
RepositoryBeanDefinitionBuilder builder = new RepositoryBeanDefinitionBuilder(registry, extension, this.configurationSource, this.resourceLoader, this.environment);
List<BeanComponentDefinition> definitions = new ArrayList();
StopWatch watch = new StopWatch(); watch.start();
//extension.getRepositoryConfigurations() 会扫描相应的包并找到用户自定义JpaRepository接口
Collection<RepositoryConfiguration<RepositoryConfigurationSource>> configurations = extension.getRepositoryConfigurations(this.configurationSource, this.resourceLoader, this.inMultiStoreMode);
Map<String, RepositoryConfiguration<?>> configurationsByRepositoryName = new HashMap(configurations.size());
Iterator var8 = configurations.iterator(); while(var8.hasNext()) {
RepositoryConfiguration<? extends RepositoryConfigurationSource> configuration = (RepositoryConfiguration)var8.next();
configurationsByRepositoryName.put(configuration.getRepositoryInterface(), configuration); //对于每个扫描找到的用户自定义JpaRepository,构建一个BeanDefinitionBuilder,
//就是在这个步骤中将该BeanDefinition同JpaRepositoryFactoryBean建立关系
BeanDefinitionBuilder definitionBuilder = builder.build(configuration);
extension.postProcess(definitionBuilder, this.configurationSource);
if (this.isXml) {
extension.postProcess(definitionBuilder, (XmlRepositoryConfigurationSource)this.configurationSource);
} else {
extension.postProcess(definitionBuilder, (AnnotationRepositoryConfigurationSource)this.configurationSource);
} //这里根据所发现的用户自定义JpaRepository接口的名字构造一个bean名称
AbstractBeanDefinition beanDefinition = definitionBuilder.getBeanDefinition();
beanDefinition.setResourceDescription(configuration.getResourceDescription());
String beanName = this.configurationSource.generateBeanName(beanDefinition);
if (logger.isTraceEnabled()) {
logger.trace(LogMessage.format("Spring Data %s - Registering repository: %s - Interface: %s - Factory: %s", extension.getModuleName(), beanName, configuration.getRepositoryInterface(), configuration.getRepositoryFactoryBeanClassName()));
} //设置当前BeanDefinition的属性factoryBeanObjectType为用户自定义JpaRepository接口的全限定名
beanDefinition.setAttribute("factoryBeanObjectType", configuration.getRepositoryInterface()); // 现在把这个bean注册到容器
registry.registerBeanDefinition(beanName, beanDefinition);
definitions.add(new BeanComponentDefinition(beanDefinition, beanName));
} potentiallyLazifyRepositories(configurationsByRepositoryName, registry, this.configurationSource.getBootstrapMode());
watch.stop(); return definitions;
} }

RepositoryBeanDefinitionBuilder#build

public BeanDefinitionBuilder build(RepositoryConfiguration<?> configuration) {
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(configuration.getRepositoryFactoryBeanClassName());
builder.getRawBeanDefinition().setSource(configuration.getSource());
builder.addConstructorArgValue(configuration.getRepositoryInterface());
builder.addPropertyValue("queryLookupStrategyKey", configuration.getQueryLookupStrategyKey());
builder.addPropertyValue("lazyInit", configuration.isLazyInit());
builder.setLazyInit(configuration.isLazyInit());
builder.setPrimary(configuration.isPrimary());
configuration.getRepositoryBaseClassName().ifPresent((it) -> {
builder.addPropertyValue("repositoryBaseClass", it);
});
NamedQueriesBeanDefinitionBuilder definitionBuilder = new NamedQueriesBeanDefinitionBuilder(this.extension.getDefaultNamedQueryLocation());
configuration.getNamedQueriesLocation().ifPresent(definitionBuilder::setLocations);
builder.addPropertyValue("namedQueries", definitionBuilder.build(configuration.getSource()));
this.registerCustomImplementation(configuration).ifPresent((it) -> {
builder.addPropertyReference("customImplementation", it);
builder.addDependsOn(it);
});
BeanDefinitionBuilder fragmentsBuilder = BeanDefinitionBuilder.rootBeanDefinition(RepositoryFragmentsFactoryBean.class);
List<String> fragmentBeanNames = (List)this.registerRepositoryFragmentsImplementation(configuration).map(RepositoryFragmentConfiguration::getFragmentBeanName).collect(Collectors.toList());
fragmentsBuilder.addConstructorArgValue(fragmentBeanNames);
builder.addPropertyValue("repositoryFragments", ParsingUtils.getSourceBeanDefinition(fragmentsBuilder, configuration.getSource()));
return builder;
}

我在builder()里调试了一下代码,程序执行到该方法倒数第二行代码时,可以BeanDefinitionBuilder#beanClass就是org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean。



从上面两段代码分析来看,不管用户创建多少个JpaRepository,最终注入Spring容器的bean都是来自JpaRepositoryFactoryBean工厂来创建。每个开发人员自定义的JpqRepository又都是针对不同的领域模型的,比如说UserRepository,OrderRepository,OrderLineItemRepository。

使用用户自定义的JpaRepository

@Autowired
private BookRepository bookRepository;

当你定义了的BookRepository后,在使用时又是如何从Spring容器中获取bean的。上面既然说了BeanDefinitionBuilder会和JpaRepositoryFactoryBean建立联系,那我们还是从JpaRepositoryFactoryBean入手。


public class JpaRepositoryFactoryBean<T extends Repository<S, ID>, S, ID>
extends TransactionalRepositoryFactoryBeanSupport<T, S, ID> { //构造函数,这里repositoryInter就是你自定义的JpaRepository
public JpaRepositoryFactoryBean(Class<? extends T> repositoryInterface) {
super(repositoryInterface);
} @Override
protected RepositoryFactorySupport doCreateRepositoryFactory() { Assert.state(entityManager != null, "EntityManager must not be null!"); return createRepositoryFactory(entityManager);
} //这个方法会返回JpaRepositoryFactory
protected RepositoryFactorySupport createRepositoryFactory(EntityManager entityManager) { JpaRepositoryFactory jpaRepositoryFactory = new JpaRepositoryFactory(entityManager);
jpaRepositoryFactory.setEntityPathResolver(entityPathResolver);
jpaRepositoryFactory.setEscapeCharacter(escapeCharacter); if (queryMethodFactory != null) {
jpaRepositoryFactory.setQueryMethodFactory(queryMethodFactory);
} return jpaRepositoryFactory;
} }



这段代码我们会关注两个地方,一个是构造函数,构造函数的参数repositoryInterface就是用户自定义的接口,一个是createRepositoryFactory(),Spring要创建JpaRepository的实现类,会先创建一个JpaRepositoryFactory,然后具体接口的实现类,或者叫做代理会交给该工厂类实现。

public class JpaRepositoryFactory extends RepositoryFactorySupport {

}

JpaRepositoryFactory继承了抽象类RepositoryFactorySupport,而RepositoryFactorySupport又实现了两个Spring接口BeanClassLoaderAware,BeanFactoryAware。

RepositoryFactorySupport位于spring-data-common.jar内。

RepositoryFactorySupport#getRepository

 public <T> T getRepository(Class<T> repositoryInterface, RepositoryFragments fragments) {

     //repositoryInterface为用户自定义的JpaRepository,这里为BookRepository。
RepositoryMetadata metadata = this.getRepositoryMetadata(repositoryInterface);
RepositoryComposition composition = this.getRepositoryComposition(metadata, fragments);
RepositoryInformation information = this.getRepositoryInformation(metadata, composition);
this.validate(information, composition);
//target为SimpleJpaRepository。
Object target = this.getTargetRepository(information);
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.addAdvisor(ExposeInvocationInterceptor.ADVISOR);
this.postProcessors.forEach((processor) -> {
processor.postProcess(result, information);
});
if (DefaultMethodInvokingMethodInterceptor.hasDefaultMethods(repositoryInterface)) {
result.addAdvice(new DefaultMethodInvokingMethodInterceptor());
} ProjectionFactory projectionFactory = this.getProjectionFactory(this.classLoader, this.beanFactory);
Optional<QueryLookupStrategy> queryLookupStrategy = this.getQueryLookupStrategy(this.queryLookupStrategyKey, this.evaluationContextProvider);
result.addAdvice(new QueryExecutorMethodInterceptor(information, projectionFactory, queryLookupStrategy, this.namedQueries, this.queryPostProcessors, this.methodInvocationListeners));
composition = composition.append(RepositoryFragment.implemented(target));
result.addAdvice(new RepositoryFactorySupport.ImplementationMethodExecutionInterceptor(information, composition, this.methodInvocationListeners));
//repository为SimpleJpaRepository
T repository = result.getProxy(this.classLoader); return repository;
}

这是一个关键方法,this.getTargetRepository会创建一个SimpleJpaRepository对象。该对象知道自己具体操作那个领域对象,随后又基于此类创建一个代理对象,设置Interceptor对象后返回该代理对象。 当真正的SimpleJpaRepository代理对象被创建之后,包裹该对象的JpaRepositoryFactoryBean对象就是我们最终要使用bean的FactoryBean,Spring容器中,用户自定义的bean保存的实际上是一个JpaRepositoryFactoryBean。

@Repository
@Transactional(readOnly = true)
public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T, ID> {
//这里就是我们常用的CURD方法了,终于看到了庐山真面目。
}

综上所述,注入bean实例化过程就结束了,可以进行注入了,根据上面的分析,每个用户自定义的JpaRepository实际上在Spring容器中保存的是一个JpaRepositoryFactoryBean,这是一个FactoryBean。当对JpaRepository进行注入并调用时会FactoryBean#getObject()获取要调用SimpleJpaRepository的代理对象。

总结

上面我自己提到的两个问题,到了这里我们就有一个明确的答案了,首先回答代理是什么,从上面调试代码可以看出来repository的h属性是JdkDynamicAopProxy对象。当程序执行的时候会通过调用JdkDynamicAopProxy.invoke(),比如说调用JpaRepository.findAll(), 代理对象的创建逻辑都隐藏在JdkDynamicAopProxy中,而在这里这个代理对象就是SimpleJpaRepository对象,也是你的自定义JpaRepository的实现类。

SimpleJpaRepository对象位于 org.springframework.data.jpa.repository.support包下。

SpringBoot 自动配置:Spring Data JPA的更多相关文章

  1. SpringBoot系列之Spring Data Jpa集成教程

    SpringBoot系列之Spring Data Jpa集成教程 Spring Data Jpa是属于Spring Data的一个子项目,Spring data项目是一款集成了很多数据操作的项目,其下 ...

  2. SpringBoot中使用Spring Data Jpa 实现简单的动态查询的两种方法

    软件152 尹以操 首先谢谢大佬的简书文章:http://www.jianshu.com/p/45ad65690e33# 这篇文章中讲的是spring中使用spring data jpa,使用了xml ...

  3. spring boot系列(五)spring boot 配置spring data jpa (查询方法)

    接着上面spring boot系列(四)spring boot 配置spring data jpa 保存修改方法继续做查询的测试: 1 创建UserInfo实体类,代码和https://www.cnb ...

  4. SpringBoot入门:Spring Data JPA 和 JPA(理论)

    参考链接: Spring Data JPA - Reference Documentation Spring Data JPA--参考文档 中文版 纯洁的微笑:http://www.ityouknow ...

  5. springboot(五):spring data jpa的使用

    在上篇文章springboot(二):web综合开发中简单介绍了一下spring data jpa的基础性使用,这篇文章将更加全面的介绍spring data jpa 常见用法以及注意事项 使用spr ...

  6. SpringBoot(五) :spring data jpa 的使用

    原文出处: 纯洁的微笑 在上篇文章springboot(二):web综合开发中简单介绍了一下spring data jpa的基础性使用,这篇文章将更加全面的介绍spring data jpa 常见用法 ...

  7. spring boot系列(三)spring boot 配置spring data jpa

    数据库使用MySQL,ORM使用spring data jpa 1 因此需要再pom.xml文件中添加相应jar包.如下: <!-- 引入jap --> <dependency> ...

  8. SpringBoot总结之Spring Data Jpa

    一.Spring Data Jpa简介 JPA(Java Persistence API)定义了一系列对象持久化的标准,目前实现这一规范的产品有Hibernate.TopLink等. Spring D ...

  9. spring boot系列(四)spring boot 配置spring data jpa (保存修改删除方法)

    spring boot 使用jpa在pom.xml在上文中已经介绍过.在这里直接介绍各个类文件如何编写: 代码结构: domain(存放实体类文件): repository(存放数据库操作文件,相当于 ...

随机推荐

  1. 高可用K8S构建3master+3node+keepalived+haproxy

    视频地址:https://www.bilibili.com/video/BV1w4411y7Go?p=66 所需安装包在视频评论区 安装准备 系统: CentOS-7-x86_64-Minimal-1 ...

  2. Python进行拉勾网数据爬取框架与思路

    爬取内容 用交互式的方式输入想查询的职位并循环输出职位简要信息,为了方便阅读,本文使用到的变量并不是以目标变量的英文来命名,而是为了方便而取的变量名,各位大牛请谅解.(因贵网站爬取一定量数据后需要登陆 ...

  3. javap使用

    在反编译前你当然需要先编译这个类了进入当前目录下:javac -g SynchronizedTest.java(使用-g参数是因为要得到下面javap -l时的输出需要使用此选项) 编译完成后,我们在 ...

  4. OpenResty 简介

    OpenResty 简介 OpenResty 是一个基于 Nginx 与 Lua 的高性能 Web 平台.我们知道开发 Nginx 的模块需要用 C 语言,同时还要熟悉它的源码,成本和门槛比较高.国人 ...

  5. 线程上下文类加载器(Context ClassLoader)

    1.线程上下文类加载器是从jdk1.2开始引入的,类Thread中的getContextClassLoader()与setContextClassLoader(ClassLoader c1),分别用来 ...

  6. c#中io常用操作笔记

    创建文件1 private void button1_Click(object sender, EventArgs e) { if (textBox1.Text == string.Empty) { ...

  7. 探讨EFCore如何优雅的实现读写分离

    前言     我们都知道当单库系统遇到性能瓶颈时,读写分离是首要优化手段之一.因为绝大多数系统读的比例远高于写的比例,并且大量耗时的读操作容易引起锁表导致无发写入数据,这时读写分离就更加重要了.   ...

  8. kafka如何保证消息得顺序性

    1. 问题 比如说我们建了一个 topic,有三个 partition.生产者在写的时候,其实可以指定一个 key,比如说我们指定了某个订单 id 作为 key,那么这个订单相关的数据,一定会被分发到 ...

  9. Mysql中varchar类型的猫腻!

    varchar的存储规则 4.0版本以下,varchar(20),指的是20字节,如果存放UTF8汉字时,只能存6个(每个汉字3字节). 5.0版本以上,varchar(20),指的是20字符,无论存 ...

  10. 风炫安全WEB安全学习第十七节课 使用Sqlmap自动化注入(一)

    风炫安全WEB安全学习第十七节课 使用Sqlmap自动化注入(一) sqlmap的使用 sqlmap 是一个开源渗透测试工具,它可以自动检测和利用 SQL 注入漏洞并接管数据库服务器.它具有强大的检测 ...