文章目录
前言
在软件开发中,分页是一个非常常见的需求,无论是在Web应用程序还是在移动应用程序中,我们经常需要将大量的数据分成多个页面进行展示。
本文主要实现图书列表API,使用SpringBoot集成MyBatis分页插件github.pageHelper,首先会从「自己实现分页原理」说起,再到「使用github.pageHelper实现分页以及注意点」,最后回到图书借阅系统实战项目中,「结合通用分页结果」,实现「统一规范的图书列表API」,读完本文,你就可以轻松拿捏通用分页。
一、自己实现分页
当你可以自己造轮子以后,再去学习其它分页组件,就会觉得非常Easy!
通常分页有两种实现方式:
- 将全部结果通过sql查询加载到内存中,再到内存中实现分页。 缺点显而易见,一次性加载所有数据到内存中,因为结果可能会非常大,会消耗大量的资源,甚至可能导致内存溢出,所以并不推荐!
- 只加载当页数据,这是真正我们期望的,所以我们实现这种!
接下来,我们以Java+MySQL的「伪代码」来演示实现分页的思路!
第一步,count 查询 总记录数(totalCount),计算总页数(totalPages)
接收前端参数:
// 当前页码(默认第1页)
int pageNum = 1;
// 每页记录数(默认一页10条)
int pageSize = 10;
先查总计录数和总页数的目的:返回给前端展示,让用户清楚一共多少条,一共多少页
selectcount(*)as totalCount from book where 。。。
使用Java代码计算总页数 totalPages
int totalPages =(int)Math.ceil((double) totalCount / pageSize);
第二步,limit 查询 指定页数据
limit 接受一个或两个数字参数。
我们主要使用两个参数:第一个参数指定第一个返回记录行的偏移量offset(可选),第二个参数指定返回记录行的最大数目rows;
基本语法是:
limit[offset,]rows
rows就是pageSize,所以我们需要先使用pageNum计算出offset:
if(pageNum > totalPages){
pageNum = totalPages;}int offset =(pageNum -1)* pageSize;
然后,通过limit查询指定页数据。
select*from book where 。。。 limitoffset, pageSize
二、不考虑分页的查询图书列表Mapper
有了实现的思路,在实际的开发中你会发现,所有的分页场景都是类似的逻辑,重复的代码,实现起来就是一个字:繁琐!相当的不爽,那么有没有一种让你爽的方式呢?没错,github.pageHelper组件,它可以与MyBatis等持久化框架无缝集成,帮助我们快速实现分页功能。它提供了丰富的功能和灵活的配置选项,使得分页变得非常简单。我们只需要实现【主查询SQL】,对于其它的像totalCount和totalPages等等统统不用管,这些通用功能都由它封装到内部了。
既然如此,我们就先实现不考虑分页的查询图书列表Mapper
需求:
首先,这是管理后台的图书列表,所以可以查询出所有图书。
可选的筛选条件:图书名称、图书编号、作者、图书状态(0-闲置 1-借阅中)、图书录入时间(开始时间和结束时间,YYYY-MM-DD即可)
因为是单表查询,我们仍然使用5.6 Mybatis代码生成器Mybatis Generator (MBG)实战详解 中的
example方式
。
BookServiceImpl
对于example方式,根据需求,我们主要是构建BookExample
/**
* 组装图书列表查询的BookExample
**/privateBookExamplebuildBookPageExample(BookListParamBO paramBO){BookExample example =newBookExample();BookExample.Criteria criteria = example.createCriteria();if(!StringUtils.isEmpty(paramBO.getBookName())){// 图书名称不为空, 模糊查询图书名称, 等同于sql: and book_name like 'xxx%'
criteria.andBookNameLike(paramBO.getBookName()+"%");}if(!StringUtils.isEmpty(paramBO.getBookNo())){// 图书编号不为空, 模糊查询图书编号
criteria.andBookNoLike(paramBO.getBookNo()+"%");}if(!StringUtils.isEmpty(paramBO.getAuthor())){// 作者不为空, 模糊查询作者
criteria.andAuthorLike(paramBO.getAuthor()+"%");}if(paramBO.getStatus()!=null){// 图书状态不为空, 指定查询该状态
criteria.andStatusEqualTo(paramBO.getStatus());}if(paramBO.getStartDay()!=null){// 录入开始时间不为空, >= 录入开始时间
criteria.andGmtCreateGreaterThanOrEqualTo(paramBO.getStartDay());}if(paramBO.getEndDay()!=null){// 录入结束时间不为空, <= 录入结束时间
criteria.andGmtCreateLessThanOrEqualTo(paramBO.getEndDay());}return example;}
解读:只是筛选条件比较多:
对于Criteria,实际就是Where条件,只是将sql转成了Java对象方式,你更喜欢哪种?文末投票看看吧~
然后,查询就更简单了,调用selectByExample即可~
/**
* 查询图书列表
**/privateList<Book>getBookList(BookListParamBO paramBO){// 组装ExampleBookExample example =buildBookPageExample(paramBO);// 查询return bookMapper.selectByExample(example);}
BookListParamBO
参数BookListParamBO 就是查询列表的参数BO,定义在bo包下:
@DatapublicclassBookListParamBOimplementsSerializable{privateString bookName;privateString bookNo;privateString author;privateInteger status;privateDate startDay;privateDate endDay;privateint pageNum =1;privateint pageSize =10;}
三、集成github.pageHelper并实现分页列表
上段已经实现了主查询,接下来,我们使用github.pageHelper实现通用分页!
第一步:引入pom依赖
在5.1 SpringBoot整合Mybatis, 老鸟教你五分钟学会:正确且全面的方式 中曾介绍过,SpringBoot的各种starter的用途,以及官方starter和第三方框架的starter命名规范,对于pageHelper,同样如此,只需引入
pagehelper-spring-boot-starter
!
<dependency><groupId>com.github.pagehelper</groupId><artifactId>pagehelper-spring-boot-starter</artifactId><version>1.2.10</version></dependency>
因为是通用功能,所以依赖放到
tg-book-common
中,版本号在父项目的dependencyManagement节点指定,详见 2-1. Maven 三层项目结构搭建 和 2-2. SpringBoot API开发详解
温馨提示: 加入依赖以后,别忘了刷新Maven依赖
第二步:实现分页查询
BookService方法定义
Page<BookBO>getBookPage(BookListParamBO bookListParamBO);
Page是返回结果,这是github.pageHelper定义的通用分页结果,在下面封装通用返回结果时再映射相应字段。
BookServiceImpl核心实现
这是本文的核心,也是重点!
github.pageHelper如何通过一个主查询,就能实现的分页呢?
这里介绍我比较喜欢的使用方式:
Page<XxxBO> page =PageHelper.startPage(pgeNum, pageSize).doSelectPage(()-> 你的查询方法);
不仅实现了通用分页,还非常明确且简洁。这里主要使用基础知识的是【泛型】和【Lambda表达式】,如果这两个还不熟悉的同学尽快补充一下【Java技能树】
- PageHelper.startPage返回的是
Page<E>
泛型对象
- doSelectPage传入的是ISelect接口参数
ISelect接口只有一个无参无返回值的方法
目的是调用我们的任意方法。
当然你可能好奇,这里是如何处理返回结果的,实际内部是使用的Mybatis拦截器Interceptor,自动实现的的count方法,组装的全部逻辑,感兴趣的同学可以查看其源码,这不是本文的重点,所以不做赘述!
OK,本文图书列表的分页的核心实现如下:
@OverridepublicPage<BookBO>getBookPage(BookListParamBO paramBO){// 组装ExampleBookExample example =buildBookPageExample(paramBO);// 查询并分页Page<Book> page =PageHelper.startPage(paramBO.getPageNum(), paramBO.getPageSize()).doSelectPage(()-> bookMapper.selectByExample(example));// Page<Book> 转成 Page<BookBO>Page<BookBO> pageBO =newPage<>();BeanUtils.copyProperties(page, pageBO);for(Book book : page.getResult()){BookBO bookBO =newBookBO();BeanUtils.copyProperties(book, bookBO);
pageBO.add(bookBO);}return pageBO;}
可以看到,分页的代码非常少,只是PO转BO的代码有点繁琐,不要急,后面会做单独封装,会单独写一篇详细讲解!
特别注意:
对于doSelectPage内的【Lambda表达式】请保证只调用Mapper方法,因为它只会对Mapper返回结果做Interceptor,所以如果你调用Mapper以后做了其它处理,它是无法感知到的,那也就不会生效!所以请不要那样使用!!!
BooKBO
因为筛选条件中有【图书状态】和 【录入时间】,所以在
BooKBO
中,也补充上这两个字段:
@DatapublicclassBookBOimplementsSerializable{privateInteger id;privateString bookNo;privateString bookName;privateInteger bookType;privateString author;privateString description;privateString publisher;privateDate publishDate;privateString coverImage;// 图书状态(0-闲置 1-借阅中)privateInteger status;// 录入时间privateDate gmtCreate;}
四、封装通用分页结果
上面实现了Service层,接下来在BookAdminController中定义API:
@PostMapping("/book/list")publicTgResult<BookBO>getBookList(@RequestBodyBookListParamVO bookListParamVO){returnTgResult.ok(bookService.getBookPage(bookListParamVO.toBO()));}
因为参数比较多,所以我们采用Post Body的方式,参数少的话还是推荐Get请求!
TgResult
为了支持分页,需要在通用结果 TgResult 增加分页相关返回字段,如下:
// 分页结果==========// 当前页码privateint pageNum;// 每页计录数privateint pageSize;// 总数privatelong totalCount;// 总页数privatelong totalPages;// 分页数据列表privateList<T> dataList;
并增加
ok
重载支持分页结果Page,也就是从Page中拿出有用的字段返回结前端:
publicstatic<T>TgResult<T>ok(Page<T> page){TgResult<T> result =build(true,"200","成功",null);
result.setPageNum(page.getPageNum());
result.setPageSize(page.getPageSize());
result.setTotalCount(page.getTotal());
result.setTotalPages(page.getPages());
result.setDataList(page.getResult());return result;}
BookListParamVO
@DatapublicclassBookListParamVOimplementsSerializable{privateString bookName;privateString bookNo;privateString author;privateInteger status;@JsonFormat(pattern ="yyyy-MM-dd")privateDate startDay;@JsonFormat(pattern ="yyyy-MM-dd")privateDate endDay;privateint pageNum =1;privateint pageSize =10;publicBookListParamBOtoBO(){BookListParamBO bo =newBookListParamBO();// 结束时间到当天的23:59:59,如果默认传入的是年月日,所以需要单独设置BeanUtils.copyProperties(this, bo,"endDay");
bo.setEndDay(getDayEndTime(this.endDay));return bo;}/**
* 获取指定时间的那天 23:59:59.999 的时间
*/privateDategetDayEndTime(finalDate date){if(date ==null){returnnull;}Calendar c =Calendar.getInstance();
c.setTime(date);
c.set(Calendar.HOUR_OF_DAY,23);
c.set(Calendar.MINUTE,59);
c.set(Calendar.SECOND,59);
c.set(Calendar.MILLISECOND,999);return c.getTime();}}
主要是做了时间格式处理,保证查询的结束时间为23:59:59.999 的时间
application.properties
统一返回时间格式为
yyyy-MM-dd HH:mm:ss
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
PostMan走一波
最后
想要看更多实战好文章,还是给大家推荐我的实战专栏–>《基于SpringBoot+SpringCloud+Vue前后端分离项目实战》,由我和 前端狗哥 合力打造的一款专栏,可以让你从0到1快速拥有企业级规范的项目实战经验!
具体的优势、规划、技术选型都可以在《开篇》试读!
订阅专栏后可以添加我的微信,我会为每一位用户进行针对性指导!
另外,别忘了关注我:天罡gg ,发布新文不容易错过: https://blog.csdn.net/scm_2008
版权归原作者 天罡gg 所有, 如有侵权,请联系我们删除。