重写SimpleJpaRepository的findAll方法实现分页改造
这重写SimpleJpaRepository的findAll方法实现分页改造
最近项目中提出一个需求,在查询的页码大于总页码数时,将查询页改成最后一页。
如> 总页数10,查询页数传的是11时,查询结果时间返回第10页数据。
主要是为了防止有多人操作时,删和查同时进行的问题。
背景;
项目中关于数据库查询都是使用JPA的接口
分页查询使用JPA中的findAll 传参 Pageable
查看源码不难发现
JPA findAll 操作实际上分为以下几步
- 获取pageNumber 和pageSize 获取limit 的范围【start,end】
- 执行语句 select 查询字段 from table where 条件 limit start,end 获取到具体字段数据
- 执行 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;}
回到需求,我们要做的有几个地方:
- 调整SQL语句的顺序,将count的sql语句提前,先计算总记录数和总页数,再查数据记录
- 判断当前页码是否超出总页数,并处理
- 将查询数据的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方法实现分页改造相关推荐
- 为什么使用HashMap需要重写hashcode和equals方法_为什么要重写 hashcode 和 equals 方法?...
1. 通过Hash算法来了解HashMap对象的高效性 2. 为什么要重写equals和hashCode方法 3. 对面试问题的说明 <Java 2019 超神之路> <Dubbo ...
- HashMap存自定义对象为什么要重写 hashcode 和 equals 方法?
HashMap的k放过自定义对象么? 当我们把自定义对象存入HashMap中时,如果不重写hashcode和equals这两个方法,会得不到预期的结果. class Key{private Integ ...
- 重写 button 的创建方法
重写 button 的创建方法 //sxc时时改变 // self.videoM.progress = progress; // if ([self.videoM.downloadStr isEqua ...
- 【Groovy】集合遍历 ( 使用集合的 findAll 方法查找集合中符合匹配条件的所有元素 | 代码示例 )
文章目录 一.使用集合的 findAll 方法查找集合中符合匹配条件的所有元素 1.闭包中使用 == 作为 findAll 方法的查找匹配条件 2.闭包中使用 is 作为 findAll 方法的查找匹 ...
- 【C++ 语言】面向对象 ( 继承 | 重写 | 子类调用父类方法 | 静态多态 | 动态多态 | 虚函数 | 纯虚函数 )
文章目录 类的继承 方法的重写 子类中调用父类方法 多态 虚函数 虚函数示例 纯虚函数 相关代码 类的继承 1. 继承表示 : C++ 中继承可以使用 ":" 符号 , 格式为 & ...
- java servlet init方法_JSP开发Servlet重写init()方法实例详解
jsp开发servlet重写init()方法实例详解 写一个servlet时,有时需要我们重写该servlet的初始化方法,然后,究竟是重写init(servletconfig config),还是重 ...
- 为什么要重写 hashcode 和 equals 方法?
我在面试Java初级开发的时候,经常会问:你有没有重写过hashcode方法?不少候选人直接说没写过.我就想,或许真的没写过,于是就再通过一个问题确认:你在用HashMap的时候,键(Key)部分,有 ...
- 为什么使用HashMap需要重写hashcode和equals方法_为什么要重写hashcode和equals方法?你能说清楚了吗...
我在面试Java初级开发的时候,经常会问:你有没有重写过hashcode方法?不少候选人直接说没写过.我就想,或许真的没写过,于是就再通过一个问题确认:你在用HashMap的时候,键(Key)部分,有 ...
- Collections.sort()泛型集合排序的使用,和自定义类实现Comparable<T>接口重写compareTo(T o)方法完成Collections.sort()排序,以及自定义排序规则
Collections算法类 1.Collections类是Java提供的一个集合操作工具类. 2.Collections类定义了一系列用于操作集合的静态方法,用于实现对集合元素的排序 ...
最新文章
- 一些在数字化转型的方面的公司和例子
- 服务器不显示内存条,服务器主机检测不到内存条
- 现代制造工程笔记04-精密超精密加工和特种加工(主要掌握加工原理加工条件)
- 强烈的打击感jinbiguandan
- 数据库表结构设计方法
- maven项目的创建
- redis 集群常用命令
- 安装net framework3.5提示需要.net framework3.5,错误代码0x800f081f
- 递归算法教学设计java,递归算法数字游戏教学软件的设计|java递归算法经典实例...
- matlab 平滑曲线连接_用MATLAB做数据拟合究竟有多直观
- 第一范式、第二范式、第三范式、BCNF范式的区别
- android 集成 firebase 推送
- Gnome3桌面美化
- 一个程序员近20年工资单
- 如何使用TensorFlow Hub和代码示例
- 语音如何转文字?建议收藏这些方法
- Python网络爬虫与信息提取(中国大学mooc)
- android 图片自动裁剪图片,Android图片选择到裁剪之步步深坑 – 简书
- 用::after写背景
- 转:罗永浩多年前的求职信,人家牛逼是种习惯
热门文章
- 顺风车拼车源码java_基于jsp的智能拼车-JavaEE实现智能拼车 - java项目源码
- 牛客网之SQL必知必会(2)-限定时间、日期的查询
- 竞赛练一练 第10期:Scratch 小训练,快来打卡!
- 「2013-3-24」ClipSync, Youdao Note, GNote
- 芭学园华龙苑的彩虹班值得上吗?
- buildroot mysql_buildroot httpd php
- Hyperledger Fabric 智能合约开发及 fabric-sdk-go/fabric-gateway 使用示例
- Solmyr 的小品文系列之二:模棱两可的陷阱
- Reack hooks的使用
- 存储过程实例5:存储过程执行truncate ,动态删除表,表明参数化