Spring Data JPA详解
Spring Data Jpa 是应用于Dao层的⼀个框架,简化数据库开发的,作用和Mybatis框架⼀样,但是在使用方式和底层机制是有所不同的,最明显的⼀个特点,Spring Data Jpa 开发Dao的时候,很多场景我们 连sql语句都不需要开发,且由Spring出品
Spring Data JPA 概述:
什么是 Spring Data JPA:
Spring Data JPA 是 Spring 基于JPA 规范的基础上封装的⼀套 JPA 应用框架,可使开发者用极简的代码即可实现对数据库的访问和操作,它提供了包括增删改查等在内的常用功能,学习并使用 Spring Data JPA 可以极大提高开发效率
说明:Spring Data JPA 极大简化了数据访问层代码
如何简化呢,使用了Spring Data JPA,我们Dao层中只需要写接口,不需要写实现类,就自动具有 了增删改查,分页查询等方法
使用Spring Data JPA 很多场景下不需要我们自己写sql语句
什么是JPA 规范:Java Persistence API(JPA) 是一套用于管理和持久化 Java 对象与关系数据库之间数据的标准规范,JPA 主要用来将 Java 对象与数据库中的表记录相互映射,通过对象化的方式进行数据库操作,简化了数据库的访问
注意了,对应的与ORM的区别是:ORM 是一种技术,它通过将数据库表映射到对象来实现对象和关系数据之间的转换,ORM 的目标是使开发者能够以面向对象的方式与数据库交互,而不必编写大量的 SQL 语句,典型是mybatis,通常使用一些xml来对应类,而JPA规范通常是使用一些注解来进行操作,所以如果mybatis本身也可以完全使用注解来完成,那么也可以称mybatis是一种JPA规范操作的框架(虽然现实并不是)
简单来说:
JPA是一种标准规范,定义了如何通过注解和实体类进行对象关系映射(ORM),JPA 注重自动化,通过注解和标准 API(如 EntityManager)来管理实体的生命周期、关系和持久化操作
ORM侧重于映射,如MyBatis 提供了一种将 SQL 查询直接映射到 Java 方法的方式,并且允许开发者手动编写 SQL 查询,这使得它非常灵活,但与 JPA 的自动化和规范化有很大的不同,但是呢,JPA最终也是操作映射的,所以可以称JPA也是一种ORM的规范,即JPA是一个实现了ORM概念的Java规范(前提是有使用了这个规范的框架,否则只是规范)
Spring Data 家族:

Spring Data JPA,JPA规范和Hibernate之间的 关系
Spring Data JPA 是 Spring 提供的⼀个封装了JPA 操作的框架,而JPA 仅仅是规范,单独使用规范无法 具体做什么,那么Spring Data JPA 、 JPA规范 以及 Hibernate (是JPA 规范的⼀种实现,了解这个即可)之间的关系是什么:

即:JPA 是⼀套规范,内部是由接口和抽象类组成的,Hiberanate 是⼀套成熟的 ORM 框架,而且 Hiberanate 实现了 JPA 规范,所以可以称 Hiberanate 为 JPA 的⼀种实现方式,我们使用 JPA 的 API 编程,意味着站在更高的角度去看待问题(面向接口编程), Spring Data JPA 是 Spring 提供的⼀套对 JPA 操作更加⾼级的封装,是在 JPA 规范下的专门用来进行数 据持久化的解决方案
也就是说,Spring Data JPA封装后,需要使用该jpa规范的框架,可以直接使用封装好的(也就是Spring Data JPA),而不用自行实现了(或者不用使用自身的,因为Spring Data JPA在原来的jpa基础上有所增强)
也要注意:虽然JPA定义了接口和规范,但它并没有提供实际的实现代码,因此,需要一个具体的实现来执行这些操作,Hibernate是JPA规范的一个常见实现,提供了实际的持久化逻辑,所以当Hibernate操作时,通常建议与该jpa整合,使得Hibernate也使用这个增强的规范,使得我们在这个规范下编写代码,从而不用自身的不增强的jpa了
说了这么多,其实就一句话:有jpa规范的框架,自身jpa还不够,我需要增强的jpa(Spring Data JPA)
Spring Data JPA 应用:
需求:使用 Spring Data JPA 完成对 tb_resume 表(简历表)的Dao 层操作(增删改查,排序, 分页等)
创建数据库:
CREATE DATABASE wd CHARACTER SET utf8;
USE wd;
CREATE TABLE tb_resume(
id BIGINT(20) NOT NULL AUTO_INCREMENT,
address VARCHAR(255) DEFAULT NULL,
NAME VARCHAR(255) DEFAULT NULL,
phone VARCHAR(255) DEFAULT NULL,
PRIMARY KEY (`id`)
);
INSERT INTO tb_resume VALUES (1, '北京', '张三', '131000000');
INSERT INTO tb_resume VALUES (2, '上海', '李四', '151000000');
INSERT INTO tb_resume VALUES (3, '广州', '王五', '153000000');
开发步骤:
我们这里操作的是整合Hibernate的,而不是单纯的Hibernate,所以呢,如果需要学习单纯的Hibernate,那么可以百度(使用整合的其实就够啦,这个整合其实不只是jpa规范继续增强,也与Spring整合了,类似mybatis整合spring,只不过这里多了一个jpa规范而已,或者说多几个注解)
首先创建项目,然后我们引入依赖:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>jpa</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>2.1.8.RELEASE</version>
</dependency>
<dependency>
<groupId>org.glassfish.web</groupId>
<artifactId>javax.el</artifactId>
<version>2.2.6</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.13</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.12.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>5.1.12.RELEASE</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>5.4.0.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-validator</artifactId>
<version>5.4.0.Final</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.46</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.21</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.1.12.RELEASE</version>
</dependency>
</dependencies>
</project>
创建jdbc.properties文件:
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/wd?characterEncoding=utf8&useSSL=false
jdbc.username=root
jdbc.password=123456
创建com.pojo包,然后创建Resume类:
package com.pojo;
import javax.persistence.*;
@Entity
@Table(name = "tb_resume")
public class Resume {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
@Column(name = "name")
private String name;
@Column(name = "address")
private String address;
@Column(name = "phone")
private String phone;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
@Override
public String toString() {
return "Resume{" +
"id=" + id +
", name='" + name + '\'' +
", address='" + address + '\'' +
", phone='" + phone + '\'' +
'}';
}
}
创建com.dao包,然后创建ResumeDao接口:
package com.dao;
import com.pojo.Resume;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
public interface ResumeDao extends JpaRepository<Resume, Long>,
JpaSpecificationExecutor<Resume> {
}
配置 Spring 的配置文件,spring.xml:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jpa="http://www.springframework.org/schema/data/jpa"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/data/jpa
https://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
<context:component-scan base-package="com"/>
<context:property-placeholder location="classpath:jdbc.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="packagesToScan" value="com.pojo"/>
<property name="persistenceProvider">
<bean class="org.hibernate.jpa.HibernatePersistenceProvider"></bean>
</property>
<property name="jpaDialect">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect"></bean>
</property>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
<property name="generateDdl" value="false"/>
<property name="database" value="MYSQL"/>
<property name="databasePlatform" value="org.hibernate.dialect.MySQLDialect"/>
<property name="showSql" value="true"/>
</bean>
</property>
</bean>
<jpa:repositories base-package="com.dao" entity-manager-factory-ref="entityManagerFactory"
transaction-manager-ref="transactionManager"/>
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
<property name="entityManagerFactory" ref="entityManagerFactory"/>
</bean>
<tx:annotation-driven/>
</beans>
至此,对应的spring扫描,以及数据源配置,以及jpa工厂配置(整合了hibernate)
我们在测试资源文件夹下创建com.test包,然后创建Test1类:
package com.test;
import com.dao.ResumeDao;
import com.pojo.Resume;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.util.Optional;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:spring.xml"})
public class Test1 {
@Autowired
private ResumeDao resumeDao;
@Test
public void testFindById() {
Optional<Resume> optional = resumeDao.findById(1l);
Resume resume = optional.get();
System.out.println(resume);
}
}
直接进行访问,若出现了数据,代表操作成功,至此我们搭建好了环境的处理
我们继续学习:
继续在该测试类中补充如下:
@Test
public void testFindOne(){
Resume resume = new Resume();
resume.setId(1l);
Example<Resume> of = Example.of(resume);
Optional<Resume> one = resumeDao.findOne(of);
System.out.println(one.get());
}
执行看看结果,然后我们继续补充:
@Test
public void testSave(){
Resume resume = new Resume();
resume.setId(1l);
resume.setName("让人人");
Resume save = resumeDao.save(resume);
resume.setName("他人");
System.out.println(save);
resume.setId(null);
Resume save1 = resumeDao.save(resume);
resume.setName("和");
System.out.println(save1);
}
只所以会造成更新和新增的实例不同,根本原因还是方法的原因,可能在以后的版本中会有所改变,这里了解即可
上面的操作方式有点像mybatis-plus(mp),只是细节不同而已,当然了,mp通常不能自动的解决复杂sql(需要写sql,即半自动,虽然mp还有对应的依赖进行增强实现全自动,但那是其他依赖,不是mp自身存在的,了解即可),而jpa通常是可以的(即全自动,这里我们看后面)
如果考虑mp的增强实现全自动,那么对应的依赖加上mp(一般可以找到:mybatis-plus-join依赖)和jpa只是一种设计理念和操作方式不同的框架而已,具体使用谁,看自身情况
我们继续:
@Test
public void testDelete(){
resumeDao.deleteById(2l);
}
我们继续:
@Test
public void testFindAll(){
List<Resume> all = resumeDao.findAll();
for (Resume resume : all) {
System.out.println(resume);
}
}
@Test
public void testSort(){
Sort sort = new Sort(Sort.Direction.DESC,"id");
List<Resume> list = resumeDao.findAll(sort);
for (int i = 0; i < list.size(); i++) {
Resume resume = list.get(i);
System.out.println(resume);
}
}
上面基本都是使用继承的接口的方法,我们也可以自定义一个:
回到前面的接口ResumeDao,在里面加上如下:
@Query("from Resume where id =?1")
public List<Resume> findByJpql(Long id);
我们回到测试类,然后加上如下:
@Test
public void testJpql() {
List<Resume> byJpql = resumeDao.findByJpql(6l);
for (Resume s : byJpql) {
System.out.println(s);
}
}
我们还可以继续补充参数:
@Query("from Resume where id =?1 and name = ?2")
public List<Resume> findByJpql(Long id,String name);
修改一下:
@Test
public void testJpql() {
List<Resume> byJpql = resumeDao.findByJpql(6l,"让人");
for (Resume s : byJpql) {
System.out.println(s);
}
}
进行测试吧,我们还可以操作原生的sql,我们在ResumeDao接口里面继续添加:
@Query(value = "select * from tb_resume where id =?1 and name = ?2",nativeQuery = true)
public List<Resume> findBySql(Long id,String name);
继续测试:
@Test
public void testSql() {
List<Resume> sql = resumeDao.findBySql(6l, "让人");
for (Resume s : sql) {
System.out.println(s);
}
}
注意:占位的基本都是解决了对应的sql注入,所以知道即可,还有我们这里还是建议使用原生的,这样不用框架来处理了,当然了,直接的使用原生的,可能只是对使用的那个数据库进行支持,并且不够动态(考虑修改类属性时,原生的没有提示),但是对操作更加复杂的sql有方便的处理
我们还可以这样:
在对应的ResumeDao接口中加上如下:
public List<Resume> findByNameLike(String name);
public List<Resume> findByName(String name);
我们进行测试:
@Test
public void testMethodName(){
List<Resume> resumes = resumeDao.findByNameLike("他%");
for (Resume resume : resumes) {
System.out.println(resume);
}
List<Resume> resumes1 = resumeDao.findByName("他");
for (Resume resume : resumes1) {
System.out.println(resume);
}
}
当然了,如果存在注解,那么操作注解的,如果存在继承的,那么操作继承的
现在我们已经说明了四种:
还有一种:动态查询,其中这里也是继承的(接口可以多继承,也可以说是第一种操作),我们看接口:
public interface ResumeDao extends JpaRepository<Resume, Long>,
JpaSpecificationExecutor<Resume> {
}
public interface JpaSpecificationExecutor<T> {
Optional<T> findOne(@Nullable Specification<T> var1);
List<T> findAll(@Nullable Specification<T> var1);
Page<T> findAll(@Nullable Specification<T> var1, Pageable var2);
List<T> findAll(@Nullable Specification<T> var1, Sort var2);
long count(@Nullable Specification<T> var1);
}
我们点开上面的Specification:
public interface Specification<T> extends Serializable {
long serialVersionUID = 1L;
static <T> Specification<T> not(Specification<T> spec) {
return Specifications.negated(spec);
}
static <T> Specification<T> where(Specification<T> spec) {
return Specifications.where(spec);
}
default Specification<T> and(Specification<T> other) {
return Specifications.composed(this, other, CompositionType.AND);
}
default Specification<T> or(Specification<T> other) {
return Specifications.composed(this, other, CompositionType.OR);
}
@Nullable
Predicate toPredicate(Root<T> var1, CriteriaQuery<?> var2, CriteriaBuilder var3);
}
那么我们直接在测试类中加上如下:
@Test
public void testSpecification() {
Specification<Resume> objectSpecification = new Specification() {
@Override
public Predicate toPredicate(Root root, CriteriaQuery criteriaQuery, CriteriaBuilder criteriaBuilder) {
Path name = root.get("name");
Predicate predicate = criteriaBuilder.equal(name, "他");
return predicate;
}
};
Optional<Resume> one = resumeDao.findOne(objectSpecification);
Resume resume = one.get();
System.out.println(resume);
}
继续测试一下:
@Test
public void testSpecificationTest() {
Specification<Resume> objectSpecification = new Specification() {
@Override
public Predicate toPredicate(Root root, CriteriaQuery criteriaQuery, CriteriaBuilder criteriaBuilder) {
Path name = root.get("name");
Path address = root.get("address");
Predicate predicate = criteriaBuilder.equal(name, "让人");
Predicate predicate1 = criteriaBuilder.like(address.as(String.class), "分%");
Predicate and = criteriaBuilder.and(predicate, predicate1);
return and;
}
};
Optional<Resume> one = resumeDao.findOne(objectSpecification);
Resume resume = one.get();
System.out.println(resume);
}
考虑一个问题,为什么要补充address.as(String.class),虽然说是变成字符串,会加上引号,但是在sql中,如果我们默认将所有数据都加上引号,不就行了,也就是无论是否是字符串都加上,为什么不这样处理,其实大多数框架或者其框架里面操作sql的部分通常都会考虑到是否添加,有些框架可能都加上单引号,有些可能为了严谨而不加上,而是看情况,大多数都是看情况的(如mybatis,通常考虑其底层逻辑的处理,如预处理对象:PreparedStatement,在41章博客有说明,mybatis内部一般是操作这个预处理对象的,然后操作数据库的),而这里通常也是,所以也可以不用设置as(String.class),具体可以自行测试
操作一下分页:
@Test
public void testPage() {
Pageable pageable = PageRequest.of(1, 2);
Page<Resume> all = resumeDao.findAll(pageable);
System.out.println(all);
List<Resume> collect = all.get().collect(Collectors.toList());
for (Resume resume : collect) {
System.out.println(resume);
}
System.out.println(all.get().count());
System.out.println(all.getTotalElements());
}
Spring Data JPA 执行过程源码分析:
Spring Data Jpa 源码很少有人去分析,因为Spring Data Jpa 地位没有之前学习的框架高,习惯把它当成⼀个工具来用,并且接口的实现对象肯定是通过动态代理来完成的(也就是增强),且代理对象的产生过程追源码很难追,所以少有人分析
一般这个代理对象是这样的:

也就是这个类:
@Repository
@Transactional(
readOnly = true
)
public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T, ID> {
}
@NoRepositoryBean
public interface JpaRepositoryImplementation<T, ID> extends JpaRepository<T, ID>, JpaSpecificationExecutor<T> {
void setRepositoryMethodMetadata(CrudMethodMetadata var1);
default void setEscapeCharacter(EscapeCharacter escapeCharacter) {
}
}
public interface ResumeDao extends JpaRepository<Resume, Long>,
JpaSpecificationExecutor<Resume> {
}
注意:上面不是代理对象的,是操作代理拦截后的返回的结果显示(当然,对应的调试显示也会因为是toString的结果,而这个toString的结果是代理接口的实现他的类的toString,如果有多个实现类,那么看你操作谁的方法了(创建代理中的指定的实现类的处理,如指定谁的实现接口,因为是匿名的,值已经得到了哦),就操作谁的toString),所以会出现对应的类,其接口的对象在上面的显示是:$Proxy39
所以说,对应的ResumeDao是代理对象,一般我们可以称这三个为代理对象,比如:
1:jdk代理生成的对象
2:jdk代理对象拦截后返回的对象(也就是上面的SimpleJpaRepository)
3:封装了jdk代理或者其他代理操作的对象
这三个都可以称为代理对象,只不过我们通常以第一个为主,所以如果在后面或者前面说明是代理对象时,应该就是这三个之中的
这里为了进行区分:我们称接口的对象是代理对象,其返回的对象是代理所产生的对象,那么ResumeDao就是代理对象
他使用什么代理呢,是JDK 动态代理
一般来说:在使用 JDK 动态代理时,每次调用 Proxy.newProxyInstance()方法都会创建一个新的代理对象,这些代理对象是由 JVM 在运行时动态生成的,并加载这些字节码来创建代理类的实例(也就是考虑jvm,或者java自身的处理字节码文件,并考虑使用类加载器的操作)
在 JDK 动态代理中,生成的代理类的名称是根据一定的规则生成的,通常遵循以下格式:
当然,也可能存在有些框架不返回代理的可能,也就是说,在内部得到代理对象后,本质上又创建了一个实例返回给接口(考虑接口的子类),该子类他内部处理对应的代理对象,所以他本身不是代理对象,只不过间接的处理了代理对象而已
那么我们先拿取之前的代码:
@Test
public void testFindById() {
Optional<Resume> optional = resumeDao.findById(1l);
Resume resume = optional.get();
System.out.println(resume);
}
在这里进行分析吧
开始分析:
首先我们需要找到产生的过程,一般由于他是Spring来赋值的,自然在Spring中进行处理,而Spring中存在AbstractApplicationContext的refresh方法中进行加载的,一般是这里(之前学习了Spring的底层原理,在那里可以找到):
public abstract class AbstractApplicationContext extends DefaultResourceLoader
implements ConfigurableApplicationContext {
}
finishBeanFactoryInitialization(beanFactory);
给上面打上断点,然后进行启动:
进入上面的方法:
public abstract class AbstractApplicationContext extends DefaultResourceLoader implements ConfigurableApplicationContext {
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
if (beanFactory.containsBean("conversionService") && beanFactory.isTypeMatch("conversionService", ConversionService.class)) {
beanFactory.setConversionService((ConversionService)beanFactory.getBean("conversionService", ConversionService.class));
}
if (!beanFactory.hasEmbeddedValueResolver()) {
beanFactory.addEmbeddedValueResolver((strVal) -> {
return this.getEnvironment().resolvePlaceholders(strVal);
});
}
String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);
String[] var3 = weaverAwareNames;
int var4 = weaverAwareNames.length;
for(int var5 = 0; var5 < var4; ++var5) {
String weaverAwareName = var3[var5];
this.getBean(weaverAwareName);
}
beanFactory.setTempClassLoader((ClassLoader)null);
beanFactory.freezeConfiguration();
beanFactory.preInstantiateSingletons();
}
}
可以在上面进入后,看到这个:
List<String> beanNames = new ArrayList(this.beanDefinitionNames);
if (this.isFactoryBean(beanName)) {
bean = this.getBean("&" + beanName);
break;
}
给上面的if (this.isFactoryBean(beanName)) {打上断点,然后点击这个:

只有满足条件的才是断点,所以到下一个断点就会到满足条件的那个地方,这个时候继续下一步:
if (this.isFactoryBean(beanName)) {
bean = this.getBean("&" + beanName);
break;
}
那么他是怎么配置成工厂bean的,或者说对应的名称是怎么处理工厂bean的,因为我们从来没有进行处理过,我们需要这对应的这个代码中打上断点:
beanName = var2.next();
bd = this.getMergedLocalBeanDefinition(beanName);
一般情况下,被识别成工厂bean,通常在于实现类实现了对应的工厂接口,通常是:
package org.springframework.beans.factory;
import org.springframework.lang.Nullable;
public interface FactoryBean<T> {
@Nullable
T getObject() throws Exception;
@Nullable
Class<?> getObjectType();
default boolean isSingleton() {
return true;
}
}
然后在xml中进行定义,创建对象实例,然后判断,当然,并不是必须要xml,因为这里是整合的,所以对应的大概率是注解生成
即对应的代理对象,应该是读取某个地方的注解,生成实例,通过判断是否实现了对应的接口FactoryBean,然后操作getObject返回实例,该返回的实例就是代理对象
但是中间也操作了bd = this.getMergedLocalBeanDefinition(beanName);,他干什么了,我们先看在名称为resumeDao时得到的值的显示:
Root bean: class [org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean]; scope=singleton; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null
也就是:JpaRepositoryFactoryBean,虽然他是RootBeanDefinition类型的,但是对应的这个肯定与他有关系
我们进入bd = this.getMergedLocalBeanDefinition(beanName);(记得调试条件):
public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
}
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory implements AutowireCapableBeanFactory {
}
public abstract class AbstractBeanFactory extends FactoryBeanRegistrySupport implements ConfigurableBeanFactory {
protected RootBeanDefinition getMergedLocalBeanDefinition(String beanName) throws BeansException {
RootBeanDefinition mbd = (RootBeanDefinition)this.mergedBeanDefinitions.get(beanName);
return mbd != null ? mbd : this.getMergedBeanDefinition(beanName, this.getBeanDefinition(beanName));
}
}
既然从集合里面拿取,那么什么时候放入的:
可以到关于this的进行全局搜索,一般就在AbstractBeanFactory里面可以找到mergedBeanDefinitions.put,发现在:
protected RootBeanDefinition getMergedBeanDefinition(String beanName, BeanDefinition bd, @Nullable BeanDefinition containingBd) throws BeanDefinitionStoreException {
synchronized(this.mergedBeanDefinitions) {
RootBeanDefinition mbd = null;
if (containingBd == null) {
mbd = (RootBeanDefinition)this.mergedBeanDefinitions.get(beanName);
}
}
在上面方法的里面,我们给RootBeanDefinition mbd = null;,加上断点,发现有String beanName,那么继续操作条件
当然,有很多操作会操作到这里,我们只看他进行第一次put的情况,我们继续观察,这个时候可以发现BeanDefinition bd中bd是有值的,所以需要看他调用栈来确定了,看这里:

我们进入方法:
public BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException {
BeanDefinition bd = (BeanDefinition)this.beanDefinitionMap.get(beanName);
if (bd == null) {
if (this.logger.isTraceEnabled()) {
this.logger.trace("No bean named '" + beanName + "' found in " + this);
}
throw new NoSuchBeanDefinitionException(beanName);
} else {
return bd;
}
}
我们找他put方法:
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) throws BeanDefinitionStoreException {
Assert.hasText(beanName, "Bean name must not be empty");
Assert.notNull(beanDefinition, "BeanDefinition must not be null");
if (beanDefinition instanceof AbstractBeanDefinition) {
try {
((AbstractBeanDefinition)beanDefinition).validate();
} catch (BeanDefinitionValidationException var8) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName, "Validation of bean definition failed", var8);
}
}
}
在上面的 Assert.notNull(beanDefinition, “BeanDefinition must not be null”);打上断点并加上条件(有beanName),看调用栈:
我们看参数beanDefinition有值,且放入的map中就是他,那么看看调用栈中是谁先没有的:

我们继续看后面的调用栈:

上面考虑解析我们的自定义标签,一般来说,由于我们的标签存在自定义的,这里也就是jpa标签的,具体解析也由jpa来完成(spring可没有全部的解析过程,或者说解析器哦,这就需要框架来完成了),这里说明的标签一般就是:jpa:repositories
我们进入看看方法:
public class RepositoryConfigurationDelegate {
public List<BeanComponentDefinition> registerRepositoriesIn(BeanDefinitionRegistry registry, RepositoryConfigurationExtension extension) {
if (LOG.isInfoEnabled()) {
LOG.info("Bootstrapping Spring Data repositories in {} mode.", this.configurationSource.getBootstrapMode().name());
}
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();
if (LOG.isDebugEnabled()) {
LOG.debug("Scanning for repositories in packages {}.", this.configurationSource.getBasePackages().stream().collect(Collectors.joining(", ")));
}
watch.start();
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);
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);
}
AbstractBeanDefinition beanDefinition = definitionBuilder.getBeanDefinition();
String beanName = this.configurationSource.generateBeanName(beanDefinition);
if (LOG.isTraceEnabled()) {
LOG.trace("Spring Data {} - Registering repository: {} - Interface: {} - Factory: {}", new Object[]{extension.getModuleName(), beanName, configuration.getRepositoryInterface(), configuration.getRepositoryFactoryBeanClassName()});
}
beanDefinition.setAttribute("factoryBeanObjectType", configuration.getRepositoryInterface());
registry.registerBeanDefinition(beanName, beanDefinition);
definitions.add(new BeanComponentDefinition(beanDefinition, beanName));
}
}
给上面的BeanDefinitionBuilder definitionBuilder = builder.build(configuration);打上断点,不需要条件(因为没不知道实使用什么条件)
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(configuration.getRepositoryFactoryBeanClassName());
public final class BeanDefinitionBuilder {
private final AbstractBeanDefinition beanDefinition;
}
public abstract class AbstractBeanDefinition extends BeanMetadataAttributeAccessor implements BeanDefinition, Cloneable {
}
public class RootBeanDefinition extends AbstractBeanDefinition {
}
然后我们进入configuration.getRepositoryFactoryBeanClassName():
public class DefaultRepositoryConfiguration<T extends RepositoryConfigurationSource> implements RepositoryConfiguration<T> {
public String getRepositoryFactoryBeanClassName() {
return (String)this.configurationSource.getRepositoryFactoryBeanClassName().orElseGet(() -> {
return this.extension.getRepositoryFactoryBeanClassName();
});
}
}
他是一个遍历,操作自定义的,所以最终我们会得到自定义的标签处理(考虑扫描的),最终也就是:
public String getRepositoryFactoryBeanClassName() {
return JpaRepositoryFactoryBean.class.getName();
}
得到自定义标签处理了,最终把这个返回,放入到对应的map中,也就是注册过程中进行了得到(自然也就会考虑名称),然后最终被我们得到,即:
BeanDefinition bd = (BeanDefinition)this.beanDefinitionMap.get(beanName);
这样我们就得到了bean的信息了
简单来说:我们自定义标签后,spring让我们自行处理自定义的标签,我们的处理就是读取自定义标签信息,如包扫描,然后扫描到对应的接口,然后自定义一个实例信息用来进行创建实例,也就是说,对应的信息保存的不是接口或者类的全限定名,而是自定义或者说固定的,这里也就是:JpaRepositoryFactoryBean,那么根据这样的说明,本质上spring和mybatis的整合自然也是如此,当然了,单独的mybatis由于没有像spring的信息操作,所以他是单纯的搞个代理,而不是考虑spring信息的统一处理的,然后考虑代理,因为spring需要考虑很多地方的,而不是mybatis的单独一个
那么我们可以回到之前的这个地方了:
bd = this.getMergedLocalBeanDefinition(beanName);
也就是说,他拿取的是我们固定的信息,即JpaRepositoryFactoryBean,操作实例时,自然考虑操作他,所以在后面的操作bean时(spring底层原理中有部分说明),会使用JpaRepositoryFactoryBean来操作创建bean,且判断是否为工厂对象也是判断他的,我们看这个:
public class JpaRepositoryFactoryBean<T extends Repository<S, ID>, S, ID> extends TransactionalRepositoryFactoryBeanSupport<T, S, ID> {
}
public abstract class TransactionalRepositoryFactoryBeanSupport<T extends Repository<S, ID>, S, ID> extends RepositoryFactoryBeanSupport<T, S, ID> implements BeanFactoryAware {
}
public abstract class RepositoryFactoryBeanSupport<T extends Repository<S, ID>, S, ID> implements InitializingBean, RepositoryFactoryInformation<S, ID>, FactoryBean<T>, BeanClassLoaderAware, BeanFactoryAware, ApplicationEventPublisherAware {
}
既然确定了得到信息的过程,那么在确定他是工厂的情况下,我们找到他的getObject方法,一般在他的父类的:

上面选择的是Show Inherited,代表显示继承的,一般有这个就可以全部看到了(因为实现的必须要自行写上的)
或者在电脑上按:ctrl+F12(如果是笔记本,可能需要加上fn来让F12生效)

我们进入:
public class JpaRepositoryFactoryBean<T extends Repository<S, ID>, S, ID> extends TransactionalRepositoryFactoryBeanSupport<T, S, ID> {
}
public abstract class TransactionalRepositoryFactoryBeanSupport<T extends Repository<S, ID>, S, ID> extends RepositoryFactoryBeanSupport<T, S, ID> implements BeanFactoryAware {
}
public abstract class RepositoryFactoryBeanSupport<T extends Repository<S, ID>, S, ID> implements InitializingBean, RepositoryFactoryInformation<S, ID>, FactoryBean<T>, BeanClassLoaderAware, BeanFactoryAware, ApplicationEventPublisherAware {
@Nonnull
public T getObject() {
return (Repository)this.repository.get();
}
public void afterPropertiesSet() {
this.factory = this.createRepositoryFactory();
this.factory.setQueryLookupStrategyKey(this.queryLookupStrategyKey);
this.factory.setNamedQueries(this.namedQueries);
this.factory.setEvaluationContextProvider((QueryMethodEvaluationContextProvider)this.evaluationContextProvider.orElseGet(() -> {
return QueryMethodEvaluationContextProvider.DEFAULT;
}));
this.factory.setBeanClassLoader(this.classLoader);
this.factory.setBeanFactory(this.beanFactory);
if (this.publisher != null) {
this.factory.addRepositoryProxyPostProcessor(new EventPublishingRepositoryProxyPostProcessor(this.publisher));
}
RepositoryFactorySupport var10001 = this.factory;
this.repositoryBaseClass.ifPresent(var10001::setRepositoryBaseClass);
RepositoryFragments customImplementationFragment = (RepositoryFragments)this.customImplementation.map((xva$0) -> {
return RepositoryFragments.just(new Object[]{xva$0});
}).orElseGet(RepositoryFragments::empty);
RepositoryFragments repositoryFragmentsToUse = ((RepositoryFragments)this.repositoryFragments.orElseGet(RepositoryFragments::empty)).append(customImplementationFragment);
this.repositoryMetadata = this.factory.getRepositoryMetadata(this.repositoryInterface);
this.mappingContext.ifPresent((it) -> {
it.getPersistentEntity(this.repositoryMetadata.getDomainType());
});
this.repository = Lazy.of(() -> {
return (Repository)this.factory.getRepository(this.repositoryInterface, repositoryFragmentsToUse);
});
if (!this.lazyInit) {
this.repository.get();
}
}
}
他为什么可以执行afterPropertiesSet方法,因为他实现了InitializingBean接口,在spring中在实例放入map前面需要实现一系列的方法,所以这里会实现,即:调用InitializingBean接口的afterPropertiesSet方法,所以会进行设置(提一下:spring初始化并不是构造方法,只是一个单纯来执行的方法)
我们给上面的这个打上断点:
this.repository = Lazy.of(() -> {
return (Repository)this.factory.getRepository(this.repositoryInterface, repositoryFragmentsToUse);
});
直接进入到这里(是下面的,看清楚,记得跳出不相关的,以后和以前可能没有说明,这里说明一下,后面不说明了):
public abstract class RepositoryFactorySupport implements BeanClassLoaderAware, BeanFactoryAware {
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);
this.validate(information, composition);
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.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;
}
}
当然,在上面打上断点也可以,在这里:RepositoryInformation information = this.getRepositoryInformation(metadata, composition);
我们看看调试信息:

发现了没有,存在了SimpleJpaRepository:
public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T, ID> {
}
也就是说,我们看到了对应在前面的代理类里面的信息,即SimpleJpaRepository,我们看看他怎么生成的
RepositoryInformation information = this.getRepositoryInformation(metadata, composition);
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
return SimpleJpaRepository.class;
}
然后借助这个:
ProxyFactory result = new ProxyFactory();
来产生工厂,中间进行一些设置,有传递包含SimpleJpaRepository的信息,然后到这里:
T repository = result.getProxy(this.classLoader);
他是最后的返回的,然后让对应的:
@Nonnull
public T getObject() {
return (Repository)this.repository.get();
}
这个来获取
那么我们进入前面的result.getProxy(this.classLoader);:
public Object getProxy(@Nullable ClassLoader classLoader) {
return this.createAopProxy().getProxy(classLoader);
}
protected final synchronized AopProxy createAopProxy() {
if (!this.active) {
this.activate();
}
return this.getAopProxyFactory().createAopProxy(this);
}
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
if (!config.isOptimize() && !config.isProxyTargetClass() && !this.hasNoUserSuppliedProxyInterfaces(config)) {
return new JdkDynamicAopProxy(config);
} else {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: Either an interface or a target is required for proxy creation.");
} else {
return (AopProxy)(!targetClass.isInterface() && !Proxy.isProxyClass(targetClass) ? new ObjenesisCglibAopProxy(config) : new JdkDynamicAopProxy(config));
}
}
}
经过调试,对应的是jdk动态代理,并且他是操作new JdkDynamicAopProxy(config),他是代理对象工厂,我们继续回到之前的:
public Object getProxy(@Nullable ClassLoader classLoader) {
return this.createAopProxy().getProxy(classLoader);
}
这次我们进入getProxy,也就是JdkDynamicAopProxy的getProxy方法:
public Object getProxy(@Nullable ClassLoader classLoader) {
if (logger.isTraceEnabled()) {
logger.trace("Creating JDK dynamic proxy: " + this.advised.getTargetSource());
}
Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
this.findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}
最后我们返回,我们看看他的信息:

返回后的信息:

在代理中,对应的显示后面是toString的结果,但是也要注意:这通常是考虑invoke里面的,匿名得到的值(这很明显,基本是固定的返回),还有返回的这个代理,而h,则代表是使用那个类来完成这个创建的(相当于this),这里很明显是使用对应的工厂类,后面就是里面的一些设置的信息了(这些信息了解即可)
而拦截得到的结果是上面的return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);的this来处理的,那么他当前类肯定实现了InvocationHandler接口,即里面必然操作了invoke(不愧是代理工厂),里面肯定会返回对应的操作
至此:我们得到的T repository = result.getProxy(this.classLoader);中,就是一个拦截后,得到结果,注意是拦截后,也就是说,不拦截的情况下他自然是代理对象
最后对应的getObject就会得到这个值,因为:
this.repository = Lazy.of(() -> {
return (Repository)this.factory.getRepository(this.repositoryInterface, repositoryFragmentsToUse);
});
上面设置了value属性,在getObject中:
return (Repository)this.repository.get();
public T get() {
T value = this.getNullable();
if (value == null) {
throw new IllegalStateException("Expected lazy evaluation to yield a non-null value but got null!");
} else {
return value;
}
}
@Nullable
private T getNullable() {
T value = this.value;
if (this.resolved) {
return value;
} else {
value = this.supplier.get();
this.value = value;
this.resolved = true;
return value;
}
}
就返回了这个属性,所以最终得到了对应的T repository = result.getProxy(this.classLoader);
也就是说,通过getObject生成的代理对象交给ResumeDao接口赋值,然后这个接口作为代理对象的引用,在调用对应的方法时,将拦截产生的结果进行赋值,最终我们得到了对应的处理,如查询,新增,删除,修改等等
@Repository
@Transactional(
readOnly = true
)
public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T, ID> {
}
@NoRepositoryBean
public interface JpaRepositoryImplementation<T, ID> extends JpaRepository<T, ID>, JpaSpecificationExecutor<T> {
void setRepositoryMethodMetadata(CrudMethodMetadata var1);
default void setEscapeCharacter(EscapeCharacter escapeCharacter) {
}
}
public interface ResumeDao extends JpaRepository<Resume, Long>,
JpaSpecificationExecutor<Resume> {
}
当然,其ResumeDao接口里面自定义的接口方法,通常在前面spring让我们操作自定义标签时,会对其实例进行一些处理,当操作的是对应自身的接口方法时,对应的拦截产生的对象中,可能在产生过程会进行一些处理,使得SimpleJpaRepository可能会进行一些处理,在mybatis中是找到xml,读取sql,进行执行处理,将结果返回,然后我们得到,那么这里是继续读取名称或者注解,解析成sql后(自定义的部分,而不是其他的固定部分),执行sql,返回结果,即最终操作拦截后的返回,这样我们就得到了对应的查询,删除,修改,新增的结果了
但是考虑到返回结果的toString是SimpleJpaRepository的类型,那么大概率是在执行invoke方法时,传递的就是这个类,而这个类就实现了上面的对应方法,来完成拦截,如果是自定义的,那么可能是将sql作为参数传递,或者执行一些其他的方法,让该类进行处理得到结果,这里可以参照106章博客(因为在107章博客或者后面中只考虑增强,没有考虑返回,虽然mybatis的底层原理(106章博客)中考虑的返回是直接的结果,而非交给其他类执行(这里就是交给SimpleJpaRepository处理))
至此我们的Spring Data JPA源码解析说明完毕了,以后需要深入的时候,看看其他如注解的处理吧(前面的也是,如mybatis,spring,springmvc等等,都只是说明,而非深入)