这重写SimpleJpaRepository的findAll方法实现分页改造

最近项目中提出一个需求,在查询的页码大于总页码数时,将查询页改成最后一页。
如> 总页数10,查询页数传的是11时,查询结果时间返回第10页数据。
主要是为了防止有多人操作时,删和查同时进行的问题。

背景;
项目中关于数据库查询都是使用JPA的接口
分页查询使用JPA中的findAll 传参 Pageable
查看源码不难发现
JPA findAll 操作实际上分为以下几步

  1. 获取pageNumber 和pageSize 获取limit 的范围【start,end】
  2. 执行语句 select 查询字段 from table where 条件 limit start,end 获取到具体字段数据
  3. 执行 select count(id) from table where 条件 获取页数和总记录数

如果不重写findAll方法,我们需要自己在每次分页查询前先进行count运算,计算出当前请求的页码是不是超过总页数,如果超过将请求页码改为最后一页。
这样做有几个弊端:

  • 每次做分页都要进行count,代码量增加,麻烦,还有可能忘记处理。当前项目要求所有分页查业务数据的地方都做处理
  • JPA的findAll分页查询的方法本身就有count查询,自己在service里再写count,导致多次进行SQL查询,无疑会降低查询效率

首先看一下SimpleJpaRepository 源码

 public Page<T> findAll(@Nullable Specification<T> spec, Pageable pageable) {// 获取查询条件和查询语句TypedQuery<T> query = getQuery(spec, pageable);// 获取查询结果、总页数、总记录数封装成Page返回return isUnpaged(pageable) ? new PageImpl<T>(query.getResultList()): readPage(query, getDomainClass(), pageable, spec);}

修改分页操作,重点看readPage函数,实际上重写的是readPage的函数

/*** Reads the given {@link TypedQuery} into a {@link Page} applying the given {@link Pageable} and* {@link Specification}.** @param query must not be {@literal null}.* @param domainClass must not be {@literal null}.* @param spec can be {@literal null}.* @param pageable can be {@literal null}.* @return*/protected <S extends T> Page<S> readPage(TypedQuery<S> query, final Class<S> domainClass, Pageable pageable,@Nullable Specification<S> spec) {// 此处设置limit范围if (pageable.isPaged()) {query.setFirstResult((int) pageable.getOffset());query.setMaxResults(pageable.getPageSize());}return PageableExecutionUtils.getPage(query.getResultList(), pageable,() -> executeCountQuery(getCountQuery(spec, domainClass)));}

query.getResultList() 查询获取sql语句的数据结果
先查询结果,再执行executeCountQuery 进行count运算获取总页数和总记录数

query对应实现类 AbstractProducedQuery

@Override@SuppressWarnings("unchecked")public QueryImplementor setFirstResult(int startPosition) {getProducer().checkOpen();if ( startPosition < 0 ) {throw new IllegalArgumentException( "first-result value cannot be negative : " + startPosition );}queryOptions.setFirstRow( startPosition );return this;}@Override@SuppressWarnings("unchecked")public QueryImplementor setMaxResults(int maxResult) {getProducer().checkOpen();if ( maxResult < 0 ) {throw new IllegalArgumentException( "max-results cannot be negative" );}else {queryOptions.setMaxRows( maxResult );}return this;}

回到需求,我们要做的有几个地方:

  1. 调整SQL语句的顺序,将count的sql语句提前,先计算总记录数和总页数,再查数据记录
  2. 判断当前页码是否超出总页数,并处理
  3. 将查询数据的SQL的limit范围值修改

2和3归根结底就是要改变pageable的pageNumber

修改后的主要方法如下;

protected <S extends T> Page<S> readPage(TypedQuery<S> query, final Class<S> domainClass, Pageable pageable,@Nullable Specification<S> spec) {long counNum = executeCountQuery(getCountQuery(spec, domainClass));int pageNumber = pageable.getPageNumber();int pageSize = pageable.getPageSize();      if (pageable.isPaged()) {// 如果传入页码大于总页数,跳转到最后一页if(pageable.getOffset()>0 && pageNumber*pageSize>counNum) {pint lastPageNum = (int) (counNum%pageSize==0? counNum/pageSize-1: counNum/pageSize);Pageable newPageable = PageRequest.of(lastPageNum, pageSize, pageable.getSort());pageable = newPageable;}query.setFirstResult((int) pageable.getOffset());query.setMaxResults(pageable.getPageSize());}return PageableExecutionUtils.getPage(query.getResultList(), pageable,() -> counNum);}

要修改的地方已经确定
接下来的问题就是,怎么让该Springboot项目的所有JPA接口默认用这个方法呢

首先创建一个类继承SimpleJpaRepository然后按以上方式重写findAll方法,修改readPage相关逻辑

public class PageSimpleJpaRepository<T,ID> extends SimpleJpaRepository<T, ID>{public PageSimpleJpaRepository(JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager) {super(entityInformation, entityManager);}public PageSimpleJpaRepository(Class<T> domainClass, EntityManager em) {super(domainClass, em);}/** (non-Javadoc)* @see org.springframework.data.repository.PagingAndSortingRepository#findAll(org.springframework.data.domain.Pageable)*/@Overridepublic Page<T> findAll(Pageable pageable) {if (isUnpaged(pageable)) {return new PageImpl<T>(findAll());}return findAll((Specification<T>) null, pageable);}/*** 重写findAll方法*/@Overridepublic Page<T> findAll(@Nullable Specification<T> spec, Pageable pageable){TypedQuery<T> query = getQuery(spec, pageable);return isUnpaged(pageable) ? new PageImpl<T>(query.getResultList()): readPage(query, getDomainClass(), pageable, spec);}private static boolean isUnpaged(Pageable pageable) {return pageable.isUnpaged();}/*** * 重写方法,如果传入页码大于总页数,跳转到查询最后一页* Reads the given {@link TypedQuery} into a {@link Page} applying the given {@link Pageable} and* {@link Specification}.** @param query must not be {@literal null}.* @param domainClass must not be {@literal null}.* @param spec can be {@literal null}.* @param pageable can be {@literal null}.* @return*/protected <S extends T> Page<S> readPage(TypedQuery<S> query, final Class<S> domainClass, Pageable pageable,@Nullable Specification<S> spec) {long counNum = executeCountQuery(getCountQuery(spec, domainClass));int pageNumber = pageable.getPageNumber();int pageSize = pageable.getPageSize();     if (pageable.isPaged()) {// 如果传入页码大于总页数,跳转到最后一页if(pageable.getOffset()>0 && pageNumber*pageSize>counNum) {int lastPageNum = (int) (counNum%pageSize==0? counNum/pageSize-1: counNum/pageSize);Pageable newPageable = PageRequest.of(lastPageNum, pageSize, pageable.getSort());pageable = newPageable;}query.setFirstResult((int) pageable.getOffset());query.setMaxResults(pageable.getPageSize());}return PageableExecutionUtils.getPage(query.getResultList(), pageable,() -> counNum);}/*** Executes a count query and transparently sums up all values returned.** @param query must not be {@literal null}.* @return*/private static long executeCountQuery(TypedQuery<Long> query) {Assert.notNull(query, "TypedQuery must not be null!");List<Long> totals = query.getResultList();long total = 0L;for (Long element : totals) {total += element == null ? 0 : element;}return total;}
}

然后在配置类或者启动类上利用注解@EnableJpaRepositories 指定repositoryBaseClass

@EnableJpaRepositories(repositoryBaseClass = PageSimpleJpaRepository.class,………其他配置………
)

之后所有的findAll的接口方法都会指向PageSimpleJpaRepository 里的findAll方法。

完善
上面的类发现拷贝了很多父类的私有和保护类的方法,其实没必要,仔细看看源码还是有公共方法的,当时时间赶,粗心了

public Page<T> findAll(@Nullable Specification<T> spec, Pageable pageable) {TypedQuery<T> query = getQuery(spec, pageable);// the current Pageable does not contain pagination informationif (pageable.isUnpaged()) {return new PageImpl<T>(query.getResultList());}Long totalSize = super.count(spec);int pageNumber = pageable.getPageNumber();int pageSize = pageable.getPageSize();// pageNumber is errorif (pageable.getOffset() > 0 && pageNumber * pageSize > totalSize) {// reset pageNumberint resetPageNumber = (int) (totalSize / pageSize);if (totalSize > 0 && 0 == totalSize % pageSize) {resetPageNumber--;}pageable = PageRequest.of(resetPageNumber, pageSize, pageable.getSort());}// normalquery.setFirstResult((int) pageable.getOffset());query.setMaxResults(pageable.getPageSize());return PageableExecutionUtils.getPage(query.getResultList(), pageable, () -> totalSize);}

今天刚改完,还没测好,不知道是否有坑,如果写的不对,望留言指出,多谢
若有问题后续持续更新……

重写SimpleJpaRepository的findAll方法实现分页改造相关推荐

  1. 为什么使用HashMap需要重写hashcode和equals方法_为什么要重写 hashcode 和 equals 方法?...

    1. 通过Hash算法来了解HashMap对象的高效性 2. 为什么要重写equals和hashCode方法 3. 对面试问题的说明 <Java 2019 超神之路> <Dubbo ...

  2. HashMap存自定义对象为什么要重写 hashcode 和 equals 方法?

    HashMap的k放过自定义对象么? 当我们把自定义对象存入HashMap中时,如果不重写hashcode和equals这两个方法,会得不到预期的结果. class Key{private Integ ...

  3. 重写 button 的创建方法

    重写 button 的创建方法 //sxc时时改变 // self.videoM.progress = progress; // if ([self.videoM.downloadStr isEqua ...

  4. 【Groovy】集合遍历 ( 使用集合的 findAll 方法查找集合中符合匹配条件的所有元素 | 代码示例 )

    文章目录 一.使用集合的 findAll 方法查找集合中符合匹配条件的所有元素 1.闭包中使用 == 作为 findAll 方法的查找匹配条件 2.闭包中使用 is 作为 findAll 方法的查找匹 ...

  5. 【C++ 语言】面向对象 ( 继承 | 重写 | 子类调用父类方法 | 静态多态 | 动态多态 | 虚函数 | 纯虚函数 )

    文章目录 类的继承 方法的重写 子类中调用父类方法 多态 虚函数 虚函数示例 纯虚函数 相关代码 类的继承 1. 继承表示 : C++ 中继承可以使用 ":" 符号 , 格式为 & ...

  6. java servlet init方法_JSP开发Servlet重写init()方法实例详解

    jsp开发servlet重写init()方法实例详解 写一个servlet时,有时需要我们重写该servlet的初始化方法,然后,究竟是重写init(servletconfig config),还是重 ...

  7. 为什么要重写 hashcode 和 equals 方法?

    我在面试Java初级开发的时候,经常会问:你有没有重写过hashcode方法?不少候选人直接说没写过.我就想,或许真的没写过,于是就再通过一个问题确认:你在用HashMap的时候,键(Key)部分,有 ...

  8. 为什么使用HashMap需要重写hashcode和equals方法_为什么要重写hashcode和equals方法?你能说清楚了吗...

    我在面试Java初级开发的时候,经常会问:你有没有重写过hashcode方法?不少候选人直接说没写过.我就想,或许真的没写过,于是就再通过一个问题确认:你在用HashMap的时候,键(Key)部分,有 ...

  9. Collections.sort()泛型集合排序的使用,和自定义类实现Comparable<T>接口重写compareTo(T o)方法完成Collections.sort()排序,以及自定义排序规则

    Collections算法类         1.Collections类是Java提供的一个集合操作工具类. 2.Collections类定义了一系列用于操作集合的静态方法,用于实现对集合元素的排序 ...

最新文章

  1. 一些在数字化转型的方面的公司和例子
  2. 服务器不显示内存条,服务器主机检测不到内存条
  3. 现代制造工程笔记04-精密超精密加工和特种加工(主要掌握加工原理加工条件)
  4. 强烈的打击感jinbiguandan
  5. 数据库表结构设计方法
  6. maven项目的创建
  7. redis 集群常用命令
  8. 安装net framework3.5提示需要.net framework3.5,错误代码0x800f081f
  9. 递归算法教学设计java,递归算法数字游戏教学软件的设计|java递归算法经典实例...
  10. matlab 平滑曲线连接_用MATLAB做数据拟合究竟有多直观
  11. 第一范式、第二范式、第三范式、BCNF范式的区别
  12. android 集成 firebase 推送
  13. Gnome3桌面美化
  14. 一个程序员近20年工资单
  15. 如何使用TensorFlow Hub和代码示例
  16. 语音如何转文字?建议收藏这些方法
  17. Python网络爬虫与信息提取(中国大学mooc)
  18. android 图片自动裁剪图片,Android图片选择到裁剪之步步深坑 – 简书
  19. 用::after写背景
  20. 转:罗永浩多年前的求职信,人家牛逼是种习惯

热门文章

  1. 顺风车拼车源码java_基于jsp的智能拼车-JavaEE实现智能拼车 - java项目源码
  2. 牛客网之SQL必知必会(2)-限定时间、日期的查询
  3. 竞赛练一练 第10期:Scratch 小训练,快来打卡!
  4. 「2013-3-24」ClipSync, Youdao Note, GNote
  5. 芭学园华龙苑的彩虹班值得上吗?
  6. buildroot mysql_buildroot httpd php
  7. Hyperledger Fabric 智能合约开发及 fabric-sdk-go/fabric-gateway 使用示例
  8. Solmyr 的小品文系列之二:模棱两可的陷阱
  9. Reack hooks的使用
  10. 存储过程实例5:存储过程执行truncate ,动态删除表,表明参数化