• 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>
  • 相关阅读:
    函数之返回值
    函数之初识函数
    三元运算符
    枚举enumerate
    模块
    迭代器
    斐波那契
    leetcode155 最小栈
    leetcode94 二叉树的中序遍历
    leetcode20 有效的括号
  • 原文地址:https://www.cnblogs.com/ZhangZiYangDeBoKe/p/10909965.html
Copyright © 2020-2023  润新知