spring-data-jpa的源码初窥

本文参考:
https://segmentfault.com/a/1190000015047290/
https://www.yisu.com/zixun/596050.html
https://zhuanlan.zhihu.com/p/467354104

spring-data-jpa简介

JPA只是一个简化对象关系映射来管理Java应用程序中的关系数据的规范。 它提供了一个平台,可以直接使用对象而不是使用SQL语句。
JPA是一套标准,意味着,它只是一套实现ORM理论的接口。没有实现的代码。
市场上的主流的JPA框架(实现者)有: Hibernate (JBoos)、EclipseTop(Eclipse社区)、OpenJPA (Apache基金会)。
spring-data-jpa是Spring基于ORM框架、JPA规范的基础上封装的一套JPA应用框架,底层使用了Hibernate的JPA技术实现,可使开发者用极简的代码即可实现对数据的访问和操作。它提供了包括增删改查等在内的常用功能,且易于扩展。

配置

数据源使用hikari

pom中增加依赖

可以单独引用hikari的依赖

<dependency>
    <groupId>com.zaxxer</groupId>
    <artifactId>HikariCP</artifactId>
    <version>3.1.0</version>
</dependency>

如果使用的是Spring Boot 2.0或者之后的版本,不需要去单独在pom.xml文件中引入HikariCP依赖。因为默认情况下spring-boot-starter-jdbc 或者 spring-boot-starter-data-jpa 会依赖进来。

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
    <version>2.0.3.RELEASE</version>
</dependency> 

或者

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
    <version>2.0.3.RELEASE</version>
</dependency> 

我在工程中的依赖如下,引入的spring-boot-starter-data-jpa跟着父工程的版本一样,是2.7.1

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.7.1</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
    </dependencies>

配置文件修改

application.properties中增加对数据源DataSource的配置项

# JPA 配置
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.database=mysql
spring.jpa.open-in-view=false

# 数据连接池配置
spring.task.execution.pool.core-size=10
spring.task.execution.pool.max-size=10
spring.task.execution.pool.keep-alive=20s
spring.task.execution.pool.queue-capacity=1000

# 数据源配置
spring.datasource.type = com.zaxxer.hikari.HikariDataSource
spring.datasource.driver-class-name = com.mysql.jdbc.Driver
spring.datasource.url = jdbc:mysql://localhost:3306/hyh_test?useUnicode=true&characterEncoding=utf8
spring.datasource.username = root
spring.datasource.password =

# hikari相关配置
spring.datasource.hikari.minimum-idle=20
spring.datasource.hikari.maximum-pool-size=50
spring.datasource.hikari.idle-timeout=30000
spring.datasource.hikari.pool-name=FlyduckHikariCP
spring.datasource.hikari.max-lifetime=1800000
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.connection-test-query=SELECT 1

使用示例

实体类

@Entity(name = "user")
@Data
@AllArgsConstructor
@NoArgsConstructor
@Builder
@DynamicInsert
@DynamicUpdate
public class User {

    @Id
    @Column(name = "id", columnDefinition = "varchar(64)")
    @GenericGenerator(name="idGenerator", strategy="uuid")
    @GeneratedValue(generator = "idGenerator")
    private String id;

    @Column(name = "name", columnDefinition = "varchar(128) default null")
    private String name;

    @Basic
    private Integer age;

    @Column(name = "create_time")
    private Long createTime;

    @Column(name = "remark", columnDefinition = "varchar(255) default null")
    private String remark;
}

DAO层

// 此处加@Repository注解标明dao层接口,不加这些方法也能被代理和使用
@Repository
public interface UserRepository extends JpaRepository<User, Serializable>, JpaSpecificationExecutor<User> {

    User findById(String id);

    List<User> findAll();

    List<User> findAllByCreateTimeBefore(Long creatTime);

    List<User> findByName(String name);
}

controller层

@RestController
@RequestMapping("/")
public class TestController {
    @Resource
    private UserRepository userRepository;

    @GetMapping("/test/getInfo")
    public List<User> testGetUser() {
        // return userRepository.findAllByCreateTimeBefore(System.currentTimeMillis());
        return userRepository.findByName("Mary");
    }
}

JPA查询原理

初始化

spring在启动的时候会扫描继承自org.springframework.data.repository.Repositor的接口的自定义的interface中的接口,然后遍历这些接口,针对每个接口依次创建如下几个实例:

  • SimpleJpaRepository —— 用来进行默认的DAO操作,是所有Repository的默认实现
  • JpaRepositoryFactoryBean —— 装配 bean,装载了动态代理 Proxy,会以对应的 DAO 的 beanName 为 key 注册到DefaultListableBeanFactory中,在需要被注入的时候从这个 bean 中取出对应的动态代理 Proxy 注入给 DAO
  • JdkDynamicAopProxy —— 动态代理对应的InvocationHandler,负责拦截 DAO 接口的所有的方法调用,然后做相应处理,比如findByName被调用的时候会先经过这个类的 invoke 方法

在JpaRepositoryFactoryBean.getRepository()方法被调用的过程中,还是在实例化QueryExecutorMethodInterceptor这个拦截器的时候,spring会去为我们的方法创建一个PartTreeJpaQuery,在它的构造方法中同时会实例化一个PartTree对象。PartTree定义了一系列的正则表达式,全部用于截取方法名,通过方法名来分解查询的条件,排序方式,查询结果等等,这个分解的步骤是在进程启动时加载 Bean 的过程中进行的,当执行查询的时候直接取方法对应的PartTree用来进行 sql 的拼装,然后进行 DB 的查询,返回结果。

如下图所示,在实例初始化时,userRepository.findByName为该查询方法实例化PartTreeJpaQuery实例。

在这里插入图片描述

spring-data-jpa对RepositoryQuery实例

1.SimpleJpaQuery
方法头上@Query注解的nativeQuery属性缺省值为false,也就是使用JPQL,此时会创建SimpleJpaQuery实例,并通过两个StringQuery类实例分别持有query jpql语句和根据query jpql计算拼接出来的countQuery jpql语句;

2.NativeJpaQuery
方法头上@Query注解的nativeQuery属性如果显式的设置为nativeQuery=true,也就是使用原生SQL,此时就会创建NativeJpaQuery实例;

3.PartTreeJpaQuery
方法头上未进行@Query注解,将使用spring-data-jpa独创的方法名识别的方式进行sql语句拼接,此时在spring-data-jpa内部就会创建一个PartTreeJpaQuery实例;

4.NamedQuery
使用javax.persistence.NamedQuery注解访问数据库的形式,此时在spring-data-jpa内部就会根据此注解选择创建一个NamedQuery实例;

5.StoredProcedureJpaQuery
顾名思义,在Repository接口的方法头上使用org.springframework.data.jpa.repository.query.Procedure注解,也就是调用存储过程的方式访问数据库,此时在spring-data-jpa内部就会根据@Procedure注解而选择创建一个StoredProcedureJpaQuery实例。

查询

在userRepository.findByName处打断点,进行debug调试,进入方法里,发现方法被动态代理了,被代理的类是JpaRepository的一个实现SimpleJpaRepository

在这里插入图片描述

从堆栈可以看出,代理类中一直通过ReflectiveMethodInvocation的proceed方法调用拦截器Interceptor,直到找到QueryExecutorMethodInterceptor,才进入doInvoke方法,这个拦截器主要做的事情就是判断方法类型,然后执行对应的操作.

在这里插入图片描述

关于ReflectiveMethodInvocation的介绍见Spring中AOP及ReflectiveMethodInvocation逻辑简析

QueryExecutorMethodInterceptor中维护了一个Map<Method, RepositoryQuery> queries。RepositoryQuery的直接抽象子类是AbstractJpaQuery,AbstractJpaQuery中有一个JpaQueryMethod实例,JpaQueryMethod又持有一个Method实例,所以RepositoryQuery实例的用途很明显,一个RepositoryQuery代表了Repository接口中的一个方法,根据方法头上注解不同的形态,将每个Repository接口中的方法分别映射成相对应的RepositoryQuery实例。在本例中,查询方法被实例化为PartTreeJpaQuery。
在这里插入图片描述

在doInvoke方法中继续调用:RepositoryMethodInvoker.doInvoke -> AbstractJpaQuery.execute -> AbstractJpaQuery.doExecute -> JpaQueryExecution.execute -> JpaQueryExecution.doExecute
在这里插入图片描述

JpaQueryExecution.doExecute中执行查找方法,返回结果

在这里插入图片描述

最终可以看到在CriteriaQueryImpl的interpret方法中,组装sql,调用entityManager.createQuery进行查询

在这里插入图片描述

小结

  1. spring启动时会扫描所有继承自org.springframework.data.repository.Repositor的接口的自定义接口,然后为其实例化一个动态代理,同时根据它的方法名、参数等实例化具体的对应的RepositoryQuery的子类。
  2. 在方法被调用是,动态代理会通过ReflectiveMethodInvocation调用拦截器,找到QueryExecutorMethodInterceptor拦截器,最终执行JpaQueryExecution.doExecute方法,里面调用hibernate底层进行拼接sql,查询并返回结果。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值