在实际的项目开发中,常常需要使用到分页,分页方式分为两种:前端分页和后端分页。
分页前端
一次ajax
请求数据的所有记录,在然后前端缓存并且计算count
状语从句:分页逻辑,一般前端组件(例如dataTable中)会提供分页动作。
特点是:简单,很适合小规模的网络平台;当数据量大的时候会产生性能问题,在查询和网络传输的时间会很长。
后端分页
在AJAX中请求指定页码pageNum
状语从句:每页的大小pageSize
,后端查询出当页的数据返回,前端只负责渲染。
特点是:复杂一些;性能瓶颈在的MySQL的查询性能,这个当然可以调优解决一般来说,开发使用的是这种方式。
不使用分页插件的分页操作
没有在分页使用插件的时候需要先写一个查询count
的select
语句,然后再写一个真正分页查询的语句中,MySQL中有对分页的支持,通过的英文limit
[主语]
limit
关键字的用法英语谚语的英文:LIMIT [offset,] rows
offset
是相对于首行的偏移量(首行是0),rows
是返回条数。
例如:
每页5条记录,取第一页,返回的是前5条记录
select * from tableA limit 0,5;
每页5条记录,取第二页,返回的是第6条记录,到第10条记录,
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
的依赖
在http://www.mvnrepository.com/中可以发现pagehelper
有4.x
状语从句:5.x
两个版本,用法有所不同,并不是向下兼容,使用在5.x
版本的时候可能会报错
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>4.2.1</version>
</dependency>
在MyBatis的的配置文件中配置PageHelper插件
假如不配置在后面使用PageInfo
类时就会出现问题,查询查询结果输出的PageInfo
属性值基本上都是错的
配置如下
<plugins>
<!-- com.github.pagehelper为PageHelper类所在包名 -->
<plugin interceptor="com.github.pagehelper.PageHelper">
<property name="dialect" value="mysql"/>
<!-- 该参数默认为false -->
<!-- 设置为true时,会将RowBounds第一个参数offset当成pageNum页码使用 -->
<!-- 和startPage中的pageNum效果一样-->
<property name="offsetAsPageNum" value="false"/>
<!-- 该参数默认为false -->
<!-- 设置为true时,使用RowBounds分页会进行count查询 -->
<property name="rowBoundsWithCount" value="false"/>
<!-- 设置为true时,如果pageSize=0或者RowBounds.limit = 0就会查询出全部的结果 -->
<!-- (相当于没有执行分页查询,但是返回结果仍然是Page类型)-->
<property name="pageSizeZero" value="true"/>
<!-- 3.3.0版本可用 - 分页参数合理化,默认false禁用 -->
<!-- 启用合理化时,如果pageNum<1会查询第一页,如果pageNum>pages会查询最后一页 -->
<!-- 禁用合理化时,如果pageNum<1或pageNum>pages会返回空数据 -->
<property name="reasonable" value="true"/>
<!-- 3.5.0版本可用 - 为了支持startPage(Object params)方法 -->
<!-- 增加了一个`params`参数来配置参数映射,用于从Map或ServletRequest中取值 -->
<!-- 可以配置pageNum,pageSize,count,pageSizeZero,reasonable,不配置映射的用默认值 -->
<!--<property name="params" value="pageNum=start;pageSize=limit;pageSizeZero=zero;reasonable=heli;count=contsql"/>-->
</plugin>
</plugins>
的英文上面PageHelper
官方给的配置状语从句:注释,虽然写的很多,不过确实描述的很明白。
dialect
:标识是哪一种数据库,设计上必须。
offsetAsPageNum
:将RowBounds
第一个参数offset
当成pageNum
页码使用
rowBoundsWithCount
:为设置true
时,使用RowBounds
分页会进行count
查询
reasonable
:value=true
时,pageNum
小于1会查询第一页,如果pageNum
大于pageSize
会查询求最后一页
注:上面的配置只针对于pagehelper4.x版本的,如果你用的是pagehelper5.x版本就要这样配置
官方推荐
1.在MyBatis配置xml中配置拦截器插件
<!--
plugins在配置文件中的位置必须符合要求,否则会报错,顺序如下:
properties?, settings?,
typeAliases?, typeHandlers?,
objectFactory?,objectWrapperFactory?,
plugins?,
environments?, databaseIdProvider?, mappers?
-->
<plugins>
<!-- com.github.pagehelper为PageHelper类所在包名 -->
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<!-- 使用下面的方式配置参数,后面会有所有的参数介绍 -->
<property name="param1" value="value1"/>
</plugin>
</plugins>
2.在Spring配置文件中配置拦截器插件
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 注意其他配置 -->
<property name="plugins">
<array>
<bean class="com.github.pagehelper.PageInterceptor">
<property name="properties">
<!--使用下面的方式配置参数,一行配置一个 -->
<value>
params=value1
</value>
</property>
</bean>
</array>
</property>
</bean>
个人推荐使用第一种,配置如下
<!-- 配置分页插件 -->
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<!-- 设置数据库类型 Oracle,Mysql,MariaDB,SQLite,Hsqldb,PostgreSQL六种数据库-->
<property name="helperDialect" value="mysql"/>
</plugin>
</plugins>
如果4.x的的版本用了5.x的版本的信息报错如下
springboot
在启动项目的时候就会报错,报错信息有很多,主要是因为
Caused by: org.apache.ibatis.builder.BuilderException:
Error resolving class. Cause: org.apache.ibatis.type.TypeException:
Could not resolve type alias 'com.github.pagehelper.PageInterceptor'.
Caused by: org.apache.ibatis.type.TypeException:
Could not resolve type alias 'com.github.pagehelper.PageInterceptor'.
Caused by: java.lang.ClassNotFoundException:
Cannot find class: com.github.pagehelper.PageInterceptor
总的来说就是缺少了com.github.pagehelper.PageInterceptor
,这个是新版拦截器,5.x的版本才开始使用,所以在4.x的版本这样配置是不行的
那么5.x的版本的配置在pagehelper4.x上能生效吗?的英文答案不行
报错信息如下
Caused by: org.apache.ibatis.builder.BuilderException:
Error parsing SQL Mapper Configuration. Cause:
java.lang.ClassCastException: com.github.pagehelper.PageHelper
cannot be cast to org.apache.ibatis.plugin.Interceptor
Caused by: java.lang.ClassCastException:
com.github.pagehelper.PageHelper
cannot be cast to org.apache.ibatis.plugin.Interceptor
新版的拦截器PageInterceptor不能和旧版拦截器相互转换,所以还是不行的。
总的来说,pagehelper4.x就该用4.x的的配置,pagehelper5.x就用5.x的的配置(官方推荐)
项目中使用方法和结果
在配置完的MyBatis后,我简单的说下pagehelper的业务用法,以就分页查询用户列表为例
添加查询所以用户的mapper
接口,对应的SQL语句我就不写了
List<UserVo> listUser();
重点来了,在然后service
中,先开启分页,把然后查询查询查询结果集放入PageInfo
中
public PageInfo listUserByPage(int pageNum, int pageSize) {
PageHelper.startPage(pageNum, pageSize);
List<UserVo> userVoList=userMapper.listUser();
PageInfo pageInfo=new PageInfo(userVoList);
return pageInfo;
}
PageHelper.startPage(pageNum, pageSize);
这句非常重要,这段代码表示分页的开始,的英文意思第从pageNum
页开始,显示每页pageSize
条记录。
PageInfo
这个类是插件里的类,这个类里面的属性会在输出结果中显示,
使用PageInfo
这个类,需要你查询将出来的list
放进去:
PageHelper输出的数据结构
在然后controller
层调用该方法设置对应的pageNum
状语从句:pageSize
就可以了,设置我pageNum
为1,pageSize
为5,看个输出结果吧
{
"msg": "获取第1页用户信息成功",
"code": 200,
"data": {
"pageNum": 1,
"pageSize": 5,
"size": 5,
"orderBy": null,
"startRow": 1,
"endRow": 5,
"total": 11,
"pages": 3,
"list": [
{
"userId": "a24d0c3b-2786-11e8-9835-e4f89cdc0d1f",
"username": "2015081040"
},
{
"userId": "b0bc9e45-2786-11e8-9835-e4f89cdc0d1f",
"username": "2015081041"
},
{
"userId": "b44fd6ac-2786-11e8-9835-e4f89cdc0d1f",
"username": "2015081042"
},
{
"userId": "b7ac58f7-2786-11e8-9835-e4f89cdc0d1f",
"username": "2015081043"
},
{
"userId": "bbdeb5d8-2786-11e8-9835-e4f89cdc0d1f",
"username": "2015081044"
}
],
"prePage": 0,
"nextPage": 2,
"isFirstPage": true,
"isLastPage": false,
"hasPreviousPage": false,
"hasNextPage": true,
"navigatePages": 8,
"navigatepageNums": [
1,
2,
3
],
"navigateFirstPage": 1,
"navigateLastPage": 3,
"firstPage": 1,
"lastPage": 3
},
"success": true,
"error": null
}
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安全调用
1.使用RowBounds
和PageRowBounds
参数方式是极其安全的
2。使用参数方式是极其安全的
3。使用ISelect接口调用是极其安全的
ISelect
接口方式除了可以保证安全外,还特别实现了将要查询转换为单纯的计数查询方式,这个方法可以将任意的查询方法,变成一个select count(*)
的查询方法
.4。什么时候会导致不安全的分页?
PageHelper方法使用了静态的ThreadLocal
参数,分页参数和线程是绑定的。只要
你可以保证在PageHelper方法调用后紧跟MyBatis查询方法,这就是安全的。因为PageHelper在finally
代码段中清除自动了ThreadLocal
存储的对象。
如果代码在进入Executor
前发生异常,就会导致线程不可用,这属于人为的Bug(例如接口方法和XML中的不匹配,导致找不到MappedStatement
时),这种情况由于线程不可用,也不会导致ThreadLocal参数被错误的使用。
但是如果你写出下面这样的代码,就是不安全的用法:
PageHelper.startPage(1, 10);
List<Country> list;
if(param1 != null){
list = countryMapper.selectIf(param1);
} else {
list = new ArrayList<Country>();
}
情况这种下由于param1
存在null
的情况,就会导致PageHelper生产了一个分页参数,但是没有被消费,这个参数就会一直保留在这个线程上。当这个线程再次被使用时,就可能导致不该分页的方法去消费这个分页参数,这就产生了莫名其妙的分页。
上面这个代码,应该写成下面这个样子:
List<Country> list;
if(param1 != null){
PageHelper.startPage(1, 10);
list = countryMapper.selectIf(param1);
} else {
list = new ArrayList<Country>();
}
这种写法就能保证安全。
</div>