MyBatis的分页插件PageHelper

在实际的项目开发中,常常需要使用到分页,分页方式分为两种:前端分页和后端分页。

分页前端
一次ajax请求数据的所有记录,在然后前端缓存并且计算count状语从句:分页逻辑,一般前端组件(例如dataTable中)会提供分页动作。
特点是:简单,很适合小规模的网络平台;当数据量大的时候会产生性能问题,在查询和网络传输的时间会很长

后端分页
在AJAX中请求指定页码pageNum状语从句:每页的大小pageSize,后端查询出当页的数据返回,前端只负责渲染。
特点是:复杂一些;性能瓶颈在的MySQL的查询性能,这个当然可以调优解决一般来说,开发使用的是这种方式。

不使用分页插件的分页操作

没有在分页使用插件的时候需要先写一个查询countselect语句,然后再写一个真正分页查询的语句中,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/中可以发现pagehelper4.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查询
reasonablevalue=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.使用RowBoundsPageRowBounds参数方式是极其安全的
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>

posted on 2019-05-23 09:17  张子扬  阅读(322)  评论(1编辑  收藏  举报

导航