Spring Data JPA:JPA项目中核心场景与进阶用法介绍
repository全貌梳理
先看下Repository相关的项目心场类图 :
整体类图虽然咋看上去很庞杂,但其实主线脉络还是中核比较清晰的 。
- 先看下蓝色的景进阶用部分其实就是Repository的一整个接口定义链条 ,而橙色的法介则是我们自己自定义的一些Repository接口类,继承父层接口的项目心场所有已有能力 。
- 左侧的中核类图与接口,其实都是景进阶用JPA提供的一些用于实现或者定制查询操作的一些辅助实现类,后面章节中会看到他们的法介身影。
对主体repository层级提供的项目心场主要方法进行简单的梳理,如下 :
下面对各个repository接口进行简单的独立介绍。
JpaRepository与它的景进阶用父类们
- Repository 位于 Spring Data Common 的lib里面 ,是法介Spring Data 里面做数据库操作的最底层的抽象接口、最顶级的项目心场父类 ,源码里面其实什么方法都没有,中核仅仅起到一个标识作用 。景进阶用
- CrudRepository 作为直接继承 Repository 的次顶层接口类,看名字也可以大致猜测出其主要作用就是封装提供基础CRUD操作。
- PagingAndSortingRepository 继承自 CrudRepository ,自然也就具备了 CrudRepository 提供的全部接口能力 。此外,从其自身新提供的接口来看,增加了排序和分页查询列表的能力,非常符合其类名的含义。
JpaRepository 与其前面的几个父类相比是个特殊的存在 ,其中补充添加了一组JPA规范的接口方法 。前面的几个接口类都是Spring Data为了兼容NoSQL而进行的一些抽象封装(因为SpringData项目是一个庞大的家族,支持各种SQL与NoSQL的数据库,SpringData JPA是SpringData家族中面向SQL数据库的一个子分支项目),从 JpaRepository 开始是对关系型数据库进行抽象封装 。
从类图可以看得出来它继承了 PagingAndSortingRepository 类 ,也就继承了其所有方法 ,并且实现类也是 SimpleJpaRepository 。从类图上还可以看出 JpaRepository 继承和拥有了 QueryByExampleExecutor 的相关方法 。
通过源码和 CrudRepository 相比较,它支持Query By Example,批量删除,提高删除效率 ,手动刷新数据库的更改方法,并将默认实现的查询结果变成了List 。
额外补充一句 :
实际的项目编码中 ,大部分的场景中,我们自定义Repository都是继承 JpaRepository 来实现的 。
自定义Repository
先看个自定义Repository的例子,如下:
看下对应类图结构 ,自定义Repository继承了JpaRepository,具备了其父系所有的操作接口,此外 ,额外扩展了业务层面自定义的一些接口方法:
自定义Repository 的时候 ,继承JpaRepository需要传入两个泛型:
- 此Repository需要操作的具体Entity对象(Entity与具体DB中表映射 ,所以指定Entity也等同于指定了此Repository所对应的目标操作Table) ,
- 此Entity实体的主键数据类型(也就是第一个参数指定的Entity类中以@Id注解标识的字段的类型)
分页、排序,一招搞定
分页 ,排序使用 Pageable 对象进行传递,其中包含 Page 和 Sort 参数对象。
查询的时候,直接传递 Pageable 参数即可(注意下,如果是用原生SQL查询的方式,此法行不通,后文有详细说明)。
// 定义repository接口的时候,直接传入Pageable参数即可nList<UserEntity> findAllByDepartment(DepartmentEntity department, Pageable pageable);
还有一种特殊的分页场景。比如 ,DB表中有100w条记录 ,然后现在需要将这些数据全量的加载到ES中。如果逐条查询然后插入ES ,显然效率太慢;如果一次性全部查询出来然后直接往ES写 ,服务端内存可能会爆掉 。
这种场景 ,其实可以基于 Slice 结果对象进行实现。Slice的作用是 ,只知道是否有下一个 Slice 可用,不会执行count ,所以当查询较大的结果集时,只知道数据是足够的就可以了 ,而且相关的业务场景也不用关心一共有多少页 。
private <T extends EsDocument, F> void fullLoadToEs(IESLoadService<T, F> esLoadService) { n try { n final int batchHandleSize = 10000;n Pageable pageable = PageRequest.of(0, batchHandleSize);n do { n // 批量加载数据,返回Slice类型结果n Slice<F> entitySilce = esLoadService.slicePageQueryData(pageable);nn // 具体业务处理逻辑n List<T> esDocumentData = esLoadService.buildEsDocumentData(entitySilce);n esUtil.batchSaveOrUpdateAsync(esDocumentData);nn // 获取本次实际上加载到的具体数据量n int pageLoadedCount = entitySilce.getNumberOfElements();n if (!entitySilce.hasNext()) { n break;n }nn // 自动重置page分页参数 ,继续拉取下一批数据n pageable = entitySilce.nextPageable();n } while (true);n } catch (Exception e) { n log.error("error occurred when load data into es", e);n }n}
复杂搜索 ,其实不复杂
按照条件进行搜索查询 ,是项目中遇到的非常典型且常用的场景 。但是条件搜索也分几种场景,下面分开说下 。
简单固定场景
所谓简单固定,即查询条件就是固定的1个字段或者若干个字段,且查询字段数量不会变,比如根据部门查询具体人员列表这种 。
这种情况,我们可以简单地直接在repository中,根据命名规范定义一个接口即可。
@Repositorynpublic interface UserRepository extends JpaRepository<UserEntity, Long> { n // 根据一个固定字段查询n List<UserEntity> findAllByDepartment(DepartmentEntity department);n // 根据多个固定字段组合查询n UserEntity findFirstByWorkIdAndUserNameAndDepartment(String workId, String userName, DepartmentEntity department);n}
简单不固定场景
考虑一种场景 ,界面上需要做一个用户搜索的能力 ,要求支持根据用户名 、工号、部门 、性别 、年龄、职务等等若干个字段中的1个或者多个的组合来查询符合条件的用户信息。
显然 ,上述通过直接在repository中按照命名规则定义接口的方式行不通了。这个时候 , Example 对象便派上用场了。
其实在前面整体介绍Repository的UML图中 ,就已经有了 Example 的身影了 ,虽然这个名字起得很敷衍 ,但其功能确是挺实在的 。