SpringDataJPA系列(2)Commons核心Repository
Spring Data Commons依赖关系
我们通过 Gradle 看一下项目依赖,了解一下 Spring Data Common 的依赖关系
通过上图的项目依赖,不难发现,数据库连接用的是 JDBC,连接池用的是 HikariCP,强依赖 Hibernate;Spring Boot Starter Data JPA 依赖 Spring Data JPA;而 Spring Data JPA 依赖 Spring Data Commons。 Spring Data Commons 是终极依赖,所以它是我们重点关注对象
Repository 接口
Repository 是 Spring Data Common 里面的顶级父类接口,操作 DB 的入口类。
Resposiory 是 Spring Data 里面进行数据库操作顶级的抽象接口,里面什么方法都没有,但是如果任何接口继承它,就能得到一个 Repository,还可以实现 JPA 的一些默认实现方法。
Spring 利用 Respository 作为 DAO 操作的 Type,以及利用 Java 动态代理机制就可以实现很多功能,比如为什么接口就能实现 DB 的相关操作?这就是 Spring 框架的高明之处。
Spring 在做动态代理的时候,只要是它的子类或者实现类,再利用 T 类以及 T 类的 主键 ID 类型作为泛型的类型参数,就可以来标记出来、并捕获到要使用的实体类型,就能帮助使用者进行数据库操作。
Repository 类层次关系
查看Repository 所在源码的路径,我们可以发现JPA存储库可以分成四个大类:
-
1处:CoroutineCrudRepository 这条继承关系链是为了支持 Kotlin 语法而实现的
-
2处:ReactiveCrudRepository 这条线是响应式编程,主要支持当前 NoSQL 方面的操作,因为这方面大部分操作都是分布式的,所以由此我们可以看出 Spring Data 想统一数据操作的“野心”,即想提供关于所有 Data 方面的操作。目前 Reactive 主要有 Cassandra、MongoDB、Redis 的实现。
-
3处:RxJava2CrudRepository 这条线是为了支持 RxJava 2 做的标准响应式编程的接口,可以看到它有两个版本的实现
-
4处:CrudRepository 这条继承关系正是我们目前大多数项目开发经常使用到的 JPA 相关的操作接口
再看下Resposiory相关接口的关系图:
- Repository(org.springframework.data.repository),没有暴露任何方法
- CrudRepository(org.springframework.data.repository),简单的 Curd 方法
- PagingAndSortingRepository(org.springframework.data.repository),带分页和排序的方法
- QueryByExampleExecutor(org.springframework.data.repository.query),简单 Example 查询
- JpaRepository(org.springframework.data.jpa.repository),JPA 的扩展方法
- JpaSpecificationExecutor(org.springframework.data.jpa.repository),JpaSpecification 扩展查询
- QueryDslPredicateExecutor(org.springframework.data.querydsl),QueryDsl 的封装
- SimpleJpaRepository(org.springframework.data.jpa.repository.support),JPA 所有接口的默认实现类
- QueryDslJpaRepository(org.springframework.data.jpa.repository.support),QueryDsl 的实现类
我们使用的时候一般是使得自定义接口继承JpaRepository
分水岭JpaRepository
JpaRepository之前的几个接口都是 Spring Data 为了兼容 NoSQL 而进行的一些抽象封装,而从 JpaRepository 开始是对关系型数据库进行抽象封装。所以你使用mysql数据库的时候都是直接继承JpaRepository。
JpaRepository中主要增加的是一些批量操作的方法,其实现类也是 SimpleJpaRepository。还优化了批量删除的性能,类似于之前 SQL 的 batch 操作,并不是像上面的 deleteAll 来 for 循环删除。其中 flush() 和 saveAndFlush() 提供了手动刷新 session,把对象的值立即更新到数据库里面的机制。
我们都知道 JPA 是 由 Hibernate 实现的,所以有 session 一级缓存的机制,当调用 save() 方法的时候,数据库里面是不会立即变化的。
Resposiory接口与实现类SimpleJpaRepository
我们可以在CrudRepository、PagingAndSortingRepository等接口发现一些方法,而这些方法的实现都是在SimpleJpaRepository类中(如果是其他 NoSQL的 实现如 MongoDB,那实现就在 Spring Data MongoDB 的 jar 里面的 MongoRepositoryImpl)。
我们可以在SimpleJpaRepository源码中看到每个方法的注释,标识出了该方法是实现了哪个接口中的哪个方法
这里特别强调了一下 Delete 和 Save 方法,是因为在实际工作中,看到有的同事画蛇添足:自己在做 Save 的时候先去 Find 一下,其实是没有必要的,Spring JPA 底层都考虑到了。在进行 Update、Delete、Insert 等操作之前会通过 findById 先查询一下实体对象的 ID,然后再去对查询出来的实体对象进行保存操作。而如果在 Delete 的时候,查询到的对象不存在,则直接抛异常。
如果有些业务场景需要进行扩展了,可以继续继承此类。如 QueryDsl 的扩展(虽然不推荐使用了,但我们可以参考它的做法,自定义自己的 SimpleJpaRepository),如果能将此类里面的实现方法看透了,基本上 JPA 中的 API 就能掌握大部分内容。
UserRepository 的实现类是 Spring 启动的时候,利用 Java 动态代理机制帮我们生成的实现类,而真正的实现类就是 SimpleJpaRepository。 SimpleJpaRepository 的实现机制,是通过 EntityManger 进行实体的操作,而 JpaEntityInforMation 里面存在实体的相关信息和 Crud 方法的元数据等。
我们可以在 RepositoryFactorySupport 设置一个断点,启动的时候,在我们的断点处就会发现 UserRepository 的接口会被动态代理成 SimpleJapRepository 的实现
这里需要注意的是每一个 Repository 的子类,都会通过这里的动态代理生成实现类。
总结
在接触了 Repository 的源码之后,我们在工作中遇到过一些类似需要抽象接口和写动态代理的情况,可以从Repository 的源码中获得这些启发:
-
上面的 7 个大 Repository 接口,我们在使用的时候可以根据实际场景,来继承不同的接口,从而选择暴露不同的 Spring Data Common 给我们提供的已有接口。这其实利用了 Java 语言的 interface 特性,在这里可以好好理解一下 interface 的妙用。
-
利用源码也可以很好地理解一下 Spring 中动态代理的作用,可以利用这种思想,在改善 MyBatis 的时候使用