Java:113-Spring Data JPA详解

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><!--单元测试jar--><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope></dependency><!--spring-data-jpa 需要引⼊的jar,start--><dependency><!--存在xml配置中的命名空间:xmlns:jpa="http://www.springframework.org/schema/data/jpa所以通常需要还有对应的注解,如dao接口中的ResumeDao的@Query,在后面会知道的--><groupId>org.springframework.data</groupId><artifactId>spring-data-jpa</artifactId><version>2.1.8.RELEASE</version></dependency><dependency><!--这个依赖,通常情况下不是必须的,也就是说,可以去掉
一般解释为:如果项目中有使用 JSP,并且页面中使用了 EL 表达式,那么 javax.el 依赖是必须的
--><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><!--Spring容器,即IOC容器的使用需要这个,比如ApplicationContext--><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.1.12.RELEASE</version></dependency><dependency><!--里面存在对于的jdbc的依赖和tx依赖,相当于是操作spring-jdbc依赖,但是他依赖自身也存在代码的他一般用来操作:--><groupId>org.springframework</groupId><artifactId>spring-orm</artifactId><version>5.1.12.RELEASE</version></dependency><!--hibernate对jpa的实现jar--><dependency><!--对应的依赖,当需要指定一个框架时,如:
<property name="persistenceProvider"><bean class="org.hibernate.jpa.HibernatePersistenceProvider"></bean></property>那么就是需要的
还存在对应的注解:如后面实体类Resume的@Entity,在后面会知道的
--><groupId>org.hibernate</groupId><artifactId>hibernate-entitymanager</artifactId><version>5.4.0.Final</version></dependency><dependency><!--用于一些其他的操作,比如:
注解验证:在实体类中使用注解,如 @NotNull,@Size,@Min,@Max 等,以便在数据保存到数据库之前进行验证
当不用时,那么这个依赖可以去掉
--><groupId>org.hibernate</groupId><artifactId>hibernate-validator</artifactId><version>5.4.0.Final</version></dependency><!--mysql 数据库驱动jar--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.46</version></dependency><!--druid连接池--><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.21</version></dependency><!--spring-test,测试的--><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 实体类属性和表字段的映射关系* @Id 标识主键* @GeneratedValue 标识主键的⽣成策略* @Column 建⽴属性和字段映射*/
@Entity
@Table(name = "tb_resume")
public class Resume {@Id
/*** ⽣成策略经常使⽤的两种:* GenerationType.IDENTITY:依赖数据库中主键⾃增功能 Mysql* GenerationType.SEQUENCE:依靠序列来产⽣主键 Oracle*/@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;}@Overridepublic 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;/*** ⼀个符合SpringDataJpa要求的Dao层接⼝是需要继承JpaRepository和* JpaSpecificationExecutor* JpaRepository<操作的实体类类型,主键类型>* 封装了基本的CRUD操作* 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/beanshttps://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttps://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/txhttp://www.springframework.org/schema/tx/spring-tx.xsdhttp://www.springframework.org/schema/data/jpahttps://www.springframework.org/schema/data/jpa/spring-jpa.xsd"><!--配置spring包扫描--><context:component-scan base-package="com"/><!--对Spring和SpringDataJPA进⾏配置--><!--创建数据库连接池druid--><!--引⼊外部资源⽂件--><context:property-placeholder location="classpath:jdbc.properties"/><!--第三⽅jar中的bean定义在xml中--><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><!--配置⼀个JPA中⾮常重要的对象,entityManagerFactory,entityManager类似于mybatis中的SqlSessionentityManagerFactory类似于Mybatis中的SqlSessionFactory当然,这里的配置有点像对应的mybatis与spring整合的处理,即:<bean id="sessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">--><bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"><!--配置⼀些细节--><!--配置数据源--><property name="dataSource" ref="dataSource"/><!--配置包扫描(pojo实体类所在的包,名称处理)--><property name="packagesToScan" value="com.pojo"/><!--指定jpa的具体实现,也就是hibernate,如果你不显式地指定persistenceProvider,Spring会尝试根据类路径上的依赖自动检测一个合适的JPA提供者(如果你已经在项目中引入了Hibernate的相关依赖,Spring会自动选择Hibernate作为JPA提供者),如果没有,那么可能会出现异常
这里是需要一个实现的,前面说明了,可以搜索"需要一个具体的实现来执行这些操作"注意了:hibernate自身就有jpa规范,所以可以单独的使用hibernate,而不用进行整合,只不过Spring Data JPA是对JPA的一层抽象和增强,提供了一些方便的功能和工具,所以这里整合一下
换言之,这里的配置相当于替换了hibernate自身的jpa规范,也或者是对应的spring data jpa使用hibernate的实现--><property name="persistenceProvider"><bean class="org.hibernate.jpa.HibernatePersistenceProvider"></bean></property><!--jpa方言配置,不同的jpa实现对于类似于beginTransaction等细节实现起来是不⼀样的,所以传⼊JpaDialect具体的实现类,这里指定的是 HibernateJpaDialect,表示使用Hibernate的方言实现
问题:既然上面给了提供者,为什么还需要方言,难道不会自动给出吗,答:通常会自动的处理,只是我们建议手动的指定,防止不会出现错误(如不小心设置了其他方言)
--><property name="jpaDialect"><bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect"></bean></property><!--配置具体provider,hibearnte框架的执⾏细节--><property name="jpaVendorAdapter"><bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"><!--定义hibernate框架的⼀些细节--><!--配置数据表是否⾃动创建,因为我们会建⽴pojo和数据表之间的映射关系程序启动时,如果数据表还没有创建,是否要程序给创建⼀下--><property name="generateDdl" value="false"/><!--指定数据库的类型hibernate本身是个dao层框架,可以⽀持多种数据库类型的,这⾥就指定本次使⽤的什么数据库--><property name="database" value="MYSQL"/><!--配置数据库的方言hibernate可以帮助我们拼装sql语句,但是不同的数据库sql语法是不同的,所以需要我们注⼊具体的数据库⽅⾔,这个可以不写,因为上面指定了数据库类型,会自动考虑对应数据库的方言当然,这里可以进行覆盖,还有其实,他们两个可以独立的写的
但是我们还是建议手动的指定,防止不会出现错误(如不小心设置了其他方言)--><property name="databasePlatform" value="org.hibernate.dialect.MySQLDialect"/><!--是否显示sql操作数据库时,是否打印sql
删除了,那么对应操作代码时,可能没有sql语句看了哦--><property name="showSql" value="true"/></bean></property></bean><!--引⽤上⾯创建的entityManagerFactory<jpa:repositories> 配置jpa的dao层细节base-package:指定dao层接⼝所在包entity-manager-factory-ref指定一个,如果只存在一个可以忽略,存在多个,自动选择一个(一般是先配置的)transaction-manager-ref指定一个,如果存在多个,通常必须指定,否则可能出现异常--><jpa:repositories base-package="com.dao" entity-manager-factory-ref="entityManagerFactory"transaction-manager-ref="transactionManager"/><!--事务管理器配置jdbcTemplate/mybatis 使⽤的是DataSourceTransactionManagerjpa规范:JpaTransactionManager--><bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"><property name="entityManagerFactory" ref="entityManagerFactory"/></bean><!--声明式事务配置,通常需要操作@Transactional注解时需要,相当于扫描对应的注解使得起作用--><tx:annotation-driven/>
</beans><!--方言可以是语法(mysql),也可以是内部操作方式(jpa方言配置)-->
至此,对应的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 {// 要测试IOC哪个对象注⼊即可@Autowiredprivate ResumeDao resumeDao;/*** dao层接⼝调⽤,分成两块:* 1、基础的增删改查* 2、专⻔针对查询的详细分析使⽤*/@Testpublic void testFindById() {// 早期的版本 dao.findOne(id); 可能现在没有了Optional<Resume> optional = resumeDao.findById(1l);Resume resume = optional.get();System.out.println(resume);/*打印的结果(也包括sql语句):Hibernate: select resume0_.id as id1_0_0_, resume0_.address as address2_0_0_, resume0_.name as name3_0_0_, resume0_.phone as phone4_0_0_ from tb_resume resume0_ where resume0_.id=?Resume{id=1, name='张三', address='北京', phone='131000000'}很明显:对应的接口由于有对应的继承的泛型,那么会找到对应的类,然后通过类的信息进行处理sql语句是:类名称0_.类变量 as 类变量递增数_0_0类别名是:类名称0_还有:@Table(name = "tb_resume")和@Column(name = "id")中id是作为sql中变量存在的,如果是iD,那么就是resume0_.iD,而id和iD在mysql中是没有影响的(表字段忽略大小写)但是呢,他们是直接的指定,在mp中,是存在不指定,使用类来操作的,我们可以测试,发现他没有像mp一样的驼峰大小写,也就是说nAme就是nAme,不指定也算的(当然了,这些小细节忽略即可,没有必要的)*/}}
直接进行访问,若出现了数据,代表操作成功,至此我们搭建好了环境的处理
我们继续学习:
继续在该测试类中补充如下:
@Testpublic 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()); //get得到对应的Resume//当然了,对应get里面如果没有数据,会出现异常:/*public T get() {if (value == null) {throw new NoSuchElementException("No value present");}return value;}*///Optional在31章博客有点说明}
执行看看结果,然后我们继续补充:
@Testpublic void testSave(){Resume resume = new Resume();resume.setId(1l);resume.setName("让人人"); //其他没有写的,按照默认则是null//新增和更新都是使用Save方法的,通过是否有主键来区分(类设置了对应的注解的)Resume save = resumeDao.save(resume);//更新resume.setName("他人");System.out.println(save); //返回更新的数据,所以对上面的resume修改不会影响这个返回resume.setId(null);Resume save1 = resumeDao.save(resume);//新增resume.setName("和");System.out.println(save1); //返回要新增的数据,也就是上面的resume,所以上面的resume修改会影响这个返回//注意,在更新之前,或者更新方法执行后,会进行一次查询操作,来保证更新的数据存在//还有,如果查询的数据,与要更新的数据一样,是不会进行更新的,否则进行更新//上面的返回中,更新是创建对象得到对应的参数数据,然后返回,而新增则不是//因为考虑更新中有个查询(他只根据id查的),用这个新实例得到参数数据(如果一样的,那么不更新,如果不一样,就得到参数数据),然后进行更新//这里是可以返回对应的实例的,在mp中,通常只会考虑是否成功}
只所以会造成更新和新增的实例不同,根本原因还是方法的原因,可能在以后的版本中会有所改变,这里了解即可
上面的操作方式有点像mybatis-plus(mp),只是细节不同而已,当然了,mp通常不能自动的解决复杂sql(需要写sql,即半自动,虽然mp还有对应的依赖进行增强实现全自动,但那是其他依赖,不是mp自身存在的,了解即可),而jpa通常是可以的(即全自动,这里我们看后面)
如果考虑mp的增强实现全自动,那么对应的依赖加上mp(一般可以找到:mybatis-plus-join依赖)和jpa只是一种设计理念和操作方式不同的框架而已,具体使用谁,看自身情况
我们继续:
@Testpublic void testDelete(){resumeDao.deleteById(2l); //没有找到,也就是没有删除,就会报错}
我们继续:
 @Testpublic void testFindAll(){List<Resume> all = resumeDao.findAll(); //查询所有for (Resume resume : all) {System.out.println(resume);}}@Testpublic void testSort(){//设置id进行降序Sort sort = new Sort(Sort.Direction.DESC,"id"); //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,在里面加上如下:
  //可以引⼊jpql(jpa查询语⾔)语句进⾏查询,jpql 语句类似于sql,只不过sql操作的是数据表和字段,jpql操作的是对象和属性//⽐如 from Resume where id=xx(在hibernate中称为hql语句)@Query("from Resume where id =?1") //指定?,代表对应的给入的值,后面的1代表接口参数中的第一个//即?1代表使用Long idpublic List<Resume> findByJpql(Long id);
我们回到测试类,然后加上如下:
 @Testpublic void testJpql() {List<Resume> byJpql = resumeDao.findByJpql(6l);for (Resume s : byJpql) {System.out.println(s);}/*Hibernate:selectresume0_.id as id1_0_,resume0_.address as address2_0_,resume0_.name as name3_0_,resume0_.phone as phone4_0_from tb_resume resume0_where resume0_.id=?
Resume{id=6, name='让人', address='null', phone='null'}对比一下这个:@Query("from Resume where id =?1")
很明显,这个注解是进行解析的,from Resume代表从这个类操作,这个类通常代表所在接口类对应继承的泛型来确定
后面的则是这个类对应的id的条件,和位置还有,对应的接口可以不加返回值,只不过你不加的话,在扫描到这个注解时(对于dao层来说,对应的即有扫描,也有对应的直接读取,可以参考mybatis中对其dao的扫描操作(可以看看是否有注解),也可以参考106章博客)
进行执行时,自然也不会有返回值(反射),一般来说,如果考虑自动匹配一些返回值列表,通常需要代理(当然,代理的底层肯定是C完成),然后自行设置返回值至此我们解释完毕*/}
我们还可以继续补充参数:
 @Query("from Resume where id =?1 and name = ?2")public List<Resume> findByJpql(Long id,String name); //对应的Long id的id名称是可以随便写的
修改一下:
@Testpublic void testJpql() {List<Resume> byJpql = resumeDao.findByJpql(6l,"让人");for (Resume s : byJpql) {System.out.println(s);}}
进行测试吧,我们还可以操作原生的sql,我们在ResumeDao接口里面继续添加:
 //默认情况下nativeQuery = true为false,代表操作jpql,设置为true,代表操作原生的//如果操作原生的,自然需要完整的sql,否则自然报错(sql报错,导致我们报错)@Query(value = "select * from tb_resume where id =?1 and name = ?2",nativeQuery = true)public List<Resume> findBySql(Long id,String name);
继续测试:
  @Testpublic 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);
/*
select
resume0_.id as id1_0_,
resume0_.address as address2_0_,
resume0_.name as name3_0_,
resume0_.phone as phone4_0_
from tb_resume resume0_
where resume0_.name like ? escape ? 
单独参数且默认情况下(具体为什么添加,可能还有其他原因,这里了解即可),如果escape什么都不操作,可以认为是空值,如''(上面只是打印sql,具体后面的拼接在底层代码里面,所以并不是全部需要看我们的参数的,有些可能有自动的处理的)*/public List<Resume> findByName(String name);/*select resume0_.id as id1_0_, resume0_.address as address2_0_, resume0_.name as name3_0_, resume0_.phone as phone4_0_ from tb_resume resume0_ where resume0_.name=?*///我们可以知道,对应的需要findBy开头,然后Name写上(首字母通常需要大写,否则报错,我测试了name,好像并不需要,但是还是建议大写,可能看版本),来确定操作谁,如果后面没有写具体的操作,如Like(首字母通常需要大写,否则报错),那么默认是等于的
我们进行测试:
@Testpublic 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);}}//也可以考虑
//findByNameAndAddress
//findByNameAndAddressLike
//findByNameAndLikeAddressLike(需要在后面,否则上面只是给Address操作Like)
//可以测试呗
当然了,如果存在注解,那么操作注解的,如果存在继承的,那么操作继承的
现在我们已经说明了四种:
/*
1:继承接口操作
2:使用jpql
3:原生sql
4:方法名称
*/
还有一种:动态查询,其中这里也是继承的(接口可以多继承,也可以说是第一种操作),我们看接口:
public interface ResumeDao extends JpaRepository<Resume, Long>,JpaSpecificationExecutor<Resume> {//..}
/*
在上面的JpaSpecificationExecutor中,可以看到如下:*/
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);}//用来封装条件的(一般只是查询的条件)//Root//CriteriaQuery//CriteriaBuilder@NullablePredicate toPredicate(Root<T> var1, CriteriaQuery<?> var2, CriteriaBuilder var3);
}
那么我们直接在测试类中加上如下:
@Testpublic void testSpecification() {//动态条件封装Specification<Resume> objectSpecification = new Specification() {//root:需要查询的对象属性//criteriaBuilder:构建查询条件@Overridepublic Predicate toPredicate(Root root, CriteriaQuery criteriaQuery, CriteriaBuilder criteriaBuilder) {//获取到name属性(会根据泛型找到对应类的)Path name = root.get("name");//构建条件Predicate predicate = criteriaBuilder.equal(name, "他");return predicate;}};/*select resume0_.id as id1_0_, resume0_.address as address2_0_, resume0_.name as name3_0_, resume0_.phone as phone4_0_ from tb_resume resume0_ where resume0_.name=?*///findOne只能让你查询一个,如果有多条数据,那么会报错Optional<Resume> one = resumeDao.findOne(objectSpecification);Resume resume = one.get(); //没有找到会报错的System.out.println(resume);}
继续测试一下:
@Testpublic void testSpecificationTest() {//动态条件封装Specification<Resume> objectSpecification = new Specification() {//root:需要查询的对象属性//criteriaBuilder:构建查询条件@Overridepublic Predicate toPredicate(Root root, CriteriaQuery criteriaQuery, CriteriaBuilder criteriaBuilder) {//获取到name属性(会根据泛型找到对应类的)Path name = root.get("name");Path address = root.get("address");//构建条件Predicate predicate = criteriaBuilder.equal(name, "让人");Predicate predicate1 = criteriaBuilder.like(address.as(String.class), "分%");//告诉他,拼接的时候,是字符串//简单来说,如果address对应的类型不是String,那么转化成String,否则相当于不变(或者不写)//组合Predicate and = criteriaBuilder.and(predicate, predicate1);//有顺序的,predicate的在前面return and;}};/*
select
resume0_.id as id1_0_,
resume0_.address as address2_0_,
resume0_.name as name3_0_,
resume0_.phone as phone4_0_
from tb_resume resume0_
where resume0_.name=? and (resume0_.address like ?)*/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),具体可以自行测试
操作一下分页:
 @Testpublic void testPage() {/*** 第⼀个参数:当前查询的⻚数,可以从0开始,而不是1,从1开始代表是第二页* 由于是页,所以从0开始,默认为limit 0,2(也可以是limit 2,默认起始为0),那么第一页就是limit 2,2* 这里是页,而不是起始位置* 第⼆个参数:每⻚查询的数量*/Pageable pageable = PageRequest.of(1, 2);//最终查询两个数量Page<Resume> all = resumeDao.findAll(pageable);System.out.println(all); //Page 2 of 5 containing com.pojo.Resume instancesList<Resume> collect = all.get().collect(Collectors.toList());for (Resume resume : collect) {System.out.println(resume);}/*执行了两次,一个是分页的,一个是所有的数据selectresume0_.id as id1_0_,resume0_.address as address2_0_,resume0_.name as name3_0_,resume0_.phone as phone4_0_from tb_resume resume0_ limit ?, ?select count(resume0_.id) as col_0_0_ from tb_resume resume0_*///上面Page<Resume> all = resumeDao.findAll(pageable);操作后,就会有上面两个sql的打印System.out.println(all.get().count());  //当前页的条数System.out.println(all.getTotalElements()); //总记录,select count(resume0_.id) as col_0_0_ from tb_resume resume0_}
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 动态代理中,生成的代理类的名称是根据一定的规则生成的,通常遵循以下格式:
/*
比如:com.sun.proxy.$ProxyN(名称是$ProxyN,前面的包不确定,只是个例子)
比如上面的ResumeDao的对象就是$Proxy39其中,N 是一个递增的数字,代表生成的代理类的索引号,每次创建新的代理对象时,这个索引号会递增,从而生成不同的代理类名称
例如,第一次调用 Proxy.newProxyInstance() 生成的代理类名称可能是 com.sun.proxy.$Proxy0,第二次是 com.sun.proxy.$Proxy1,以此类推,这些代理类名称都是由 JVM 自动生成的,是固定的格式,但具体的索引号会随着代理对象的创建而不断递增
那么可不可以设置名称:答,不可以
*/
当然,也可能存在有些框架不返回代理的可能,也就是说,在内部得到代理对象后,本质上又创建了一个实例返回给接口(考虑接口的子类),该子类他内部处理对应的代理对象,所以他本身不是代理对象,只不过间接的处理了代理对象而已
那么我们先拿取之前的代码:
  @Testpublic void testFindById() {Optional<Resume> optional = resumeDao.findById(1l);Resume resume = optional.get();System.out.println(resume);}
在这里进行分析吧
开始分析:
/*
打上断点
Optional<Resume> optional = resumeDao.findById(1l);*/
首先我们需要找到产生的过程,一般由于他是Spring来赋值的,自然在Spring中进行处理,而Spring中存在AbstractApplicationContext的refresh方法中进行加载的,一般是这里(之前学习了Spring的底层原理,在那里可以找到):
public abstract class AbstractApplicationContext extends DefaultResourceLoaderimplements ConfigurableApplicationContext {//..   
}
/*第⼗一步:初始化所有剩下的⾮懒加载的单例bean初始化创建⾮懒加载方式的单例Bean实例(未设置属性)填充属性初始化方法调用(比如调用afterPropertiesSet方法、init-method方法)调用BeanPostProcessor(后置处理器)对实例bean进行后置处*/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();//直接看这里:实例化所有立即加载的单例bean,我们进入这里beanFactory.preInstantiateSingletons();}//..}
可以在上面进入后,看到这个:
List<String> beanNames = new ArrayList(this.beanDefinitionNames);
//他里面就存在对应的名称:resumeDao//继续:
//我们可以到这里:if (this.isFactoryBean(beanName)) {bean = this.getBean("&" + beanName);break;}
给上面的if (this.isFactoryBean(beanName)) {打上断点,然后点击这个:

在这里插入图片描述

只有满足条件的才是断点,所以到下一个断点就会到满足条件的那个地方,这个时候继续下一步:
 if (this.isFactoryBean(beanName)) { //是FactoryBean,那么存在对应的方法(getObject方法)得到实例//所以这个bean就是我们要的关键代码,我们进入看看bean = this.getBean("&" + beanName);break;}
那么他是怎么配置成工厂bean的,或者说对应的名称是怎么处理工厂bean的,因为我们从来没有进行处理过,我们需要这对应的这个代码中打上断点:
 // 获取下一个bean名称beanName = var2.next();
//RootBeanDefinition bd;bd = this.getMergedLocalBeanDefinition(beanName);
/*
bd = this.getMergedLocalBeanDefinition(beanName); 是 Spring Framework 中的一个方法调用,用于获取给定 bean 名称的合并后的本地 Bean 定义(RootBeanDefinition),这个方法的主要作用是将 bean 定义中继承的属性、配置、父子关系等信息合并成一个完整的 bean 定义对象,便于后续实例化和管理
*/
一般情况下,被识别成工厂bean,通常在于实现类实现了对应的工厂接口,通常是:
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//package org.springframework.beans.factory;import org.springframework.lang.Nullable;public interface FactoryBean<T> {@NullableT getObject() throws Exception;@NullableClass<?> 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 { //ConfigurableListableBeanFactory//this是:DefaultListableBeanFactory对象,ConfigurableListableBeanFactory得到(DefaultListableBeanFactory的父类)/*public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {*///..protected RootBeanDefinition getMergedLocalBeanDefinition(String beanName) throws BeansException {//private final Map<String, RootBeanDefinition> mergedBeanDefinitions = new ConcurrentHashMap(256);//从集合里面拿取,拿取到了对应的RootBeanDefinitionRootBeanDefinition 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 {//也就是这个地方:beanDefinitionMap是存放bean信息的工厂,从里面拿取对应的bean信息(比如名称和全限定名称)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);}//definitionBuilder是直接得到的,所以考虑前面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; //这个beanDefinition的值就是:JpaRepositoryFactoryBean了,我们找到最终的地方了//..   
}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 {//..   
}//好吧,他有FactoryBean
//那么他的确是工厂对象
既然确定了得到信息的过程,那么在确定他是工厂的情况下,我们找到他的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 {//..@Nonnullpublic T getObject() {return (Repository)this.repository.get();}//..//这里是赋值this.repository的地方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);// 设置代理的接口,包括仓库接口、Repository 接口和 TransactionalProxy 接口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);//进入:
//在里面的Class<?> baseClass = (Class)this.repositoryBaseClass.orElse(this.getRepositoryBaseClass(metadata));中就会得到这个结果protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {return SimpleJpaRepository.class;}
//而且还是固定的指定
然后借助这个:
// 创建动态代理工厂ProxyFactory result = new ProxyFactory();
来产生工厂,中间进行一些设置,有传递包含SimpleJpaRepository的信息,然后到这里:
// 生成代理对象T repository = result.getProxy(this.classLoader);//进入:
他是最后的返回的,然后让对应的:
@Nonnullpublic 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;}}@Nullableprivate 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) {}
}//正好对应与ResumeDao的接口信息
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等等,都只是说明,而非深入)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/854295.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

天锐绿盾数据防泄密软件有哪些功能

天锐绿盾数据防泄密软件的功能丰富而全面&#xff0c;旨在从源头上保障企业数据的安全。以下是对其主要功能的归纳和介绍&#xff1a; www.drhchina.com 一、文件加密模块 透明加密&#xff1a;在不影响用户工作流程的前提下&#xff0c;对需要保护的文件进行自动加密处理。文…

别再这么起号了!TikTok小白起号误区,你中招了吗?

看过不少Tiktok新手的起号失败案例&#xff0c;总结下来就是以下这几个问题&#xff0c;今天结合一些个人起号心得给大家分享怎么成功在TK起号&#xff0c;希望对大家有所帮助。 手机/网络环境 首先我们要确保手机环境和网络环境没有问题&#xff0c;如果被TK判断出是非海外用户…

数据治理服务解决方案(35页WORD)

方案介绍&#xff1a; 本数据治理服务解决方案旨在为企业提供一站式的数据治理服务&#xff0c;包括数据规划、数据采集、数据存储、数据处理、数据质量保障、数据安全及合规等方面。通过构建完善的数据治理体系&#xff0c;确保企业数据的准确性、完整性和一致性&#xff0c;…

【Python】PySide6使用入门和注意事项

文章目录 前言关于PySide和PyQtQt Designerpyside6在vscode中ui文件转换兼容性问题主程序结构蓝牙协议初探&#xff08;应用层&#xff09; 前言 最近在开发一个带界面的软件&#xff0c;需要使用蓝牙&#xff0c;然后找到一个开源仓库使用的是Qt里面的Qbluetooth模块&#xff…

List实现类——ArrayList、LinkedList及迭代器(并发修改异常错误)源码分析

1、ArrayList本质及源码分析 两种情况&#xff1a;加一个和很多个 底层原理&#xff1a;elementData是底层数组的名字 再次满了的话&#xff0c;在扩容1.5倍 如果利用addAll一次添加多个元素&#xff0c;按实际元素数进行扩容 源码分析&#xff1a; 1、空参构造&#xff0c;…

STORM论文阅读笔记

这是篇NIPS2023的 world model 论文文章提出&#xff0c;WM的误差会在训练过程中积累从而影响policy的训练&#xff0c;向WM中加噪声可以改善这一点。其他的流程和IRIS差不多&#xff0c;差别在以下几点&#xff1a; image encoder&#xff0c;IRIS用的VQVAE, 本文用的是VAE&am…

看穿人性!现货白银交易的一些博弈心得

很多投资者认为现货白银交易最应该讲求的是交易技巧&#xff0c;但交易的技巧和套路是“死”的&#xff0c;行情走势却是“活”的&#xff0c;投资者需要在实践中不断地累积经验和总结心得&#xff0c;才能更加灵活地面对行情走势的变化&#xff0c;逐步达至盈利的理想彼岸。 无…

本地安装nightingale监控分析服务并发布公网详细流程

文章目录 前言1. Linux 部署Nightingale2. 本地访问测试3. Linux 安装cpolar4. 配置Nightingale公网访问地址5. 公网远程访问Nightingale管理界面6. 固定Nightingale公网地址 前言 本文主要介绍如何在本地Linux系统部署 Nightingale 夜莺监控并结合cpolar内网穿透工具实现远程…

Go基础编程 - 09 - 通道(channel)

通道&#xff08;channel&#xff09; 1. 声明2. channel的操作3. 无缓冲通道4. 有缓冲通道5. 如何优雅的从通道循环取值6. 单向通道7. 异常总结 上一篇&#xff1a;结构体 Go语言的并发模式&#xff1a;不要通过共享内存来通信&#xff0c;而应该通过通信来共享内存。 Go语言…

oracle安装,导出、导入domp文件、解开oracle行级锁

下载地址&#xff1a; https://www.oracle.com/database/technologies/oracle19c-windows-downloads.html 然后解压&#xff0c;请记住你的解压地址&#xff0c;也就是软件安装地址&#xff0c; 后面还会有一个数据库存储位置&#xff0c;导出的domp文件就是在这里。 然后按照…

PyQt5和Eric7的安装使用 —— Python篇

需要安装Python的朋友请看另一篇文章&#xff1a; windows系统安装Python -----并安装使用Pycharm编辑器 一、安装PyQt5&#xff1a; 1、方法一&#xff1a;使用pip命令在线安装。 输入以下命令可以直接安装&#xff1a; pip install PyQt5 由于安装默认使用国外的镜像&a…

豆浆机水位传感器工作原理

豆浆机水位传感器的工作原理基于光电效应&#xff0c;利用近红外发光二极管和光敏接收器的组合实现液位的精确检测与控制。在豆浆机内部&#xff0c;传感器安装在水箱底部或需要检测液位的位置&#xff0c;起到监测和控制豆浆机水位的重要作用。 传感器包括一个近红外发光二极…

李良济联合盒马杭州店,带你沉浸式体验中医药文化,玩转夏季养生~

6月15-16日&#xff0c;李良济携手盒马联合打造的老字号养生路演活动&#xff0c;在新开业的杭州城西银泰城盒马店强势开启&#xff01; 现场&#xff0c;既有李良济特色清凉养生茶饮&#xff0c;还有中医药文化体验活动&#xff0c;惊喜不断&#xff0c;养生不停~这个夏天在盒…

编写乘法器求解算法表达式

描述 编写一个4bit乘法器模块&#xff0c;并例化该乘法器求解c12*a5*b&#xff0c;其中输入信号a,b为4bit无符号数&#xff0c;c为输出。注意请不要直接使用*符号实现乘法功能。 模块的信号接口图如下&#xff1a; 要求使用Verilog HDL语言实现以上功能&#xff0c;并编写tes…

修改以太网卡mac地址

原生以太网卡与PCIe以太网卡 以Intel 原生以太网卡与PCIe以太网卡为例&#xff1a; Intel原生以太网卡和PCIe以太网卡在系统中实现网络连接时&#xff0c;涉及到与系统总线&#xff08;如PCIe总线&#xff09;的连接方式和性能差异。 Intel 原生以太网卡 定义&#xff1a;所…

cloud_enum:一款针对不同平台云环境安全的OSINT工具

关于cloud_enum cloud_enum是一款功能强大的云环境安全OSINT工具&#xff0c;该工具支持AWS、Azure和Google Cloud三种不同的云环境&#xff0c;旨在帮助广大研究人员枚举目标云环境中的公共资源&#xff0c;并尝试寻找其中潜在的安全威胁。 功能介绍 当前版本的cloud_enum支…

第04章:IDEA的安装与使用

第04章&#xff1a;随堂复习与企业真题&#xff08;IDEA安装与使用&#xff09; 一、随堂复习 1. IDEA的认识 IDEA(集成功能强大、符合人体工程学&#xff08;设置人性化&#xff09;)Eclipse 2. IDEA的下载、安装、卸载 卸载&#xff1a;使用控制面板进行卸载&#xff0c;…

列出docker常用的命令

一、基础命令 docker run 创建并启动一个容器 docker ps 列出当前运行的容器 docker ps -a 列出所有容器&#xff0c;包括未运行的 docker stop 停止一个运行中的容器 docker start 启动一个已停止的容器 docker restart 重启容器 docker rm 删除一个或多个容器 docker pull 从…

OpenCV 双目三角法计算点云

文章目录 一、简介二、实现代码三、实现效果参考资料一、简介 基于三角法计算点坐标的过程类似于我们人类眼睛观察事物的过程: 如上图所示,通过两个相机观察到同一位置,我们可以通过两个相机得到这一位置的投影坐标 ( u r , v r ) , ( u l , v l )

golang函数

【1】函数&#xff1a; 对特定的功能进行提取&#xff0c;形成一个代码片段&#xff0c;这个代码片段就是我们所说的函数 【2】函数的作用&#xff1a;提高代码的复用性 【3】函数和函数是并列的关系&#xff0c;所以我们定义的函数不能写到main函数中 【4】基本语法 func 函…