本文参考: 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
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.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进行查询
小结
- spring启动时会扫描所有继承自org.springframework.data.repository.Repositor的接口的自定义接口,然后为其实例化一个动态代理,同时根据它的方法名、参数等实例化具体的对应的RepositoryQuery的子类。
- 在方法被调用是,动态代理会通过ReflectiveMethodInvocation调用拦截器,找到QueryExecutorMethodInterceptor拦截器,最终执行JpaQueryExecution.doExecute方法,里面调用hibernate底层进行拼接sql,查询并返回结果。
|