在实际的项目开发中,常常需要使用到分页,分页方式分为两种:前端分页和后端分页。
前端分页
一次 ajax 请求数据的所有记录,然后在前端缓存并且计算 count 和分页逻辑,一般前端组件(例如 dataTable)会提供分页动作。
特点是:简单,很适合小规模的 web 平台;当数据量大的时候会产生性能问题,在查询和网络传输的时间会很长。
后端分页
在 ajax 请求中指定页码 pageNum 和每页的大小 pageSize ,后端查询出当页的数据返回,前端只负责渲染。
特点是:复杂一些;性能瓶颈在MySQL的查询性能,这个当然可以调优解决。一般来说,开发使用的是这种方式。
不使用分页插件的分页操作
在没有使用分页插件的时候需要先写一个查询 count
的 select
语句,然后再写一个真正分页查询的语句,MySQL 中有对分页的支持,是通过 limit 子句
limit
关键字的用法是: LIMIT [offset,] rows
offset
是相对于首行的偏移量(首行是0),rows
是返回条数。
例如:
每页5条记录,取第一页,返回的是前5条记录
1 | select * from tableA limit 0,5; |
每页5条记录,取第二页,返回的是第6条记录,到第10条记录,
1 | select * from tableA limit 5,5; |
不过当偏移量逐渐增大的时候,查询速度可能就会变慢,性能会有所下降。
使用Mybatis分页插件PageHelper
PageHelper 是一款好用的开源免费的 Mybatis 第三方物理分页插件
Github 地址:https://github.com/pagehelper/Mybatis-PageHelper
官方地址:https://pagehelper.github.io/
在 SpringBoot 中使用 PageHelper
引入依赖
在 pom.xml
文件中引入 pagehelper-spring-boot-starter
依赖
1 | <dependency> |
注意:无需任何多余配置
在 Spring 中使用 PageHelper
首先要在 pom.xml
中配置 PageHelper
的依赖
在 http://www.mvnrepository.com/ 中可以发现 pagehelper
有 4.x
和 5.x
两个版本,用法有所不同,并不是向下兼容,在使用 5.x
版本的时候可能会报错
1 | <dependency> |
在 Mybatis 的配置文件中配置 PageHelper 插件
假如不配置在后面使用 PageInfo
类时就会出现问题,输出结果的 PageInfo
属性值基本上都是错的
配置如下:
1 | <plugins> |
上面是 PageHelper
官方给的配置和注释,虽然写的很多,不过确实描述的很明白。
dialect
:标识是哪一种数据库,设计上必须。offsetAsPageNum
:将RowBounds
第一个参数offset
当成pageNum
页码使用rowBoundsWithCount
:设置为true
时,使用RowBounds
分页会进行count
查询reasonable
:value=true
时,pageNum
小于1会查询第一页,如果pageNum
大于pageSize
会查询最后一页
注:上面的配置只针对于 pagehelper4.x 版本的,如果你用的是 pagehelper5.x 版本就要这样配置
官方推荐
在 MyBatis 配置 xml 中配置拦截器插件
1 | <!-- |
或者在 Spring 配置文件中配置拦截器插件
1 | <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> |
个人推荐使用第一种,配置如下
1 | <!-- 配置分页插件 --> |
如果 4.x 的版本用了 5.x 的版本报错信息如下
springboot
在启动项目的时候就会报错,报错信息有很多,主要是因为
1 | Caused by: org.apache.ibatis.builder.BuilderException: |
总的来说就是缺少了 com.github.pagehelper.PageInterceptor
,这个是新版拦截器,5.x 版本才开始使用,所以在 4.x 版本这样配置是不行的
那么 5.x 版本的配置在 pagehelper4.x 上能生效吗?答案是不行
报错信息如下:
1 | Caused by: org.apache.ibatis.builder.BuilderException: |
新版的拦截器 PageInterceptor 不能和旧版拦截器相互转换,所以还是不行的。
总的来说,pagehelper4.x 就该用 4.x 的配置,pagehelper5.x 就用 5.x 的配置(官方推荐)
案例
项目中使用方法和结果
在配置完 mybatis 后,我简单的说下 pagehelper 的业务用法,就以分页查询用户列表为例
添加查询所以用户的 mapper
接口,对应的sql语句我就不写了
1 | List<UserVo> listUser(); |
重点来了,然后在 service
中,先开启分页,然后把查询结果集放入 PageInfo
中
1 | public PageInfo listUserByPage(int pageNum, int pageSize) { |
PageHelper.startPage(pageNum, pageSize)
;这句非常重要,这段代码表示分页的开始,意思是从第 pageNum
页开始,每页显示 pageSize
条记录。
PageInfo
这个类是插件里的类,这个类里面的属性会在输出结果中显示,
使用 PageInfo
这个类,你需要将查询出来的 list
放进去:
PageHelper 输出的数据结构
然后在 controller
层调用该方法设置对应的 pageNum
和 pageSize
就可以了,我设置 pageNum
为1, pageSize
为5,看个输出结果吧
1 | { |
PageInfo这个类里面的属性:
pageNum
当前页pageSize
每页的数量size
当前页的数量orderBy
排序startRow
当前页面第一个元素在数据库中的行号endRow
当前页面最后一个元素在数据库中的行号total
总记录数(在这里也就是查询到的用户总数)pages
总页数 (这个页数也很好算,每页5条,总共有11条,需要3页才可以显示完)list
结果集prePage
前一页nextPage
下一页isFirstPage
是否为第一页isLastPage
是否为最后一页hasPreviousPage
是否有前一页hasNextPage
是否有下一页navigatePages
导航页码数navigatepageNums
所有导航页号navigateFirstPage
导航第一页navigateLastPage
导航最后一页firstPage
第一页lastPage
最后一页
安全性
PageHelper 安全调用
- 使用 RowBounds 和 PageRowBounds参数方式是极其安全的
- 使用参数方式是极其安全的
- 使用 ISelect 接口调用是极其安全的
ISelect
接口方式除了可以保证安全外,还特别实现了将查询转换为单纯的 count 查询方式,这个方法可以将任意的查询方法,变成一个select count(*)
的查询方法。 - 什么时候会导致不安全的分页?
PageHelper 方法使用了静态的ThreadLocal
参数,分页参数和线程是绑定的。
只要你可以保证在 PageHelper 方法调用后紧跟 MyBatis 查询方法,这就是安全的。
因为 PageHelper 在 finally
代码段中自动清除了 ThreadLocal
存储的对象。
如果代码在进入 Executor
前发生异常,就会导致线程不可用,这属于人为的 Bug(例如接口方法和 XML 中的不匹配,导致找不到 MappedStatement
时), 这种情况由于线程不可用,也不会导致 ThreadLocal 参数被错误的使用。
但是如果你写出下面这样的代码,就是不安全的用法:
1 | PageHelper.startPage(1, 10); |
这种情况下由于 param1
存在 null
的情况,就会导致 PageHelper 生产了一个分页参数,但是没有被消费,这个参数就会一直保留在这个线程上。当这个线程再次被使用时,就可能导致不该分页的方法去消费这个分页参数,这就产生了莫名其妙的分页。
上面这个代码,应该写成下面这个样子:
1 | List<Country> list; |
这种写法就能保证安全。
If you like this blog or find it useful for you, you are welcome to comment on it. You are also welcome to share this blog, so that more people can participate in it. If the images used in the blog infringe your copyright, please contact the author to delete them. Thank you !