在web开发中,数据的分页是必不可少的。Pagehelper分页插件很强大,虽说平时我们不需要用到它的很多功能,但是了解下还是有必要的。

官网:https://pagehelper.github.io/

注:在 MyBatis下使用。本节是在 SpringBoot整合mybatis 基础上进行演示。

一、引入依赖

<!-- 核心启动器, 包括auto-configuration、logging and YAML -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- 数据库操作需要的mysql 驱动包 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.48</version>
</dependency>
<!-- mybatis -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.1</version>
</dependency>
<!-- pagehelper -->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <version>1.2.13</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
</dependency>

二、application.properties

#这里要注意&,可能在spring的xml中我们用的是转义符号(&amp;),但是在这里不用
spring.datasource.url=jdbc:mysql://192.168.178.5:12345/mydb?characterEncoding=utf8&useUnicode=true&useSSL=false&serverTimezone=UTC
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.type=com.zaxxer.hikari.HikariDataSource

####### mybatis #######
# 指定映射文件的具体位置
mybatis.mapper-locations=classpath:mapper/*.xml

####### pagehelper #######
# 默认情况下会使用 PageHelper 方式进行分页,如果想要实现自己的分页逻辑,
# 可以实现 Dialect(com.github.pagehelper.Dialect) 接口,然后配置该属性为实现类的全限定名称。
# 下面几个参数都是针对默认 dialect 情况下的参数。使用自定义 dialect 实现时(不推荐),下面的参数没有任何作用# 分页的数据库语言, oracle/mysql
pagehelper.helper-dialect=mysql
# 该参数对使用 RowBounds 作为分页参数时有效。 当该参数设置为 true 时,
# 会将 RowBounds 中的 offset 参数当成 pageNum 使用,可以用页码和页面大小两个参数进行分页。
pagehelper.offset-as-page-num= false
# 该参数对使用 RowBounds 作为分页参数时有效。
# 当该参数设置为true时,使用 RowBounds 分页会进行 count 查询。
pagehelper.row-bounds-with-count=false
# 当该参数设置为 true 时,如果 pageSize=0 或者 RowBounds.limit = 0
# 就会查询出全部的结果(相当于没有执行分页查询,但是返回结果仍然是 Page 类型)。
pagehelper.page-size-zero=false
# 分页合理化参数,默认值为false。当该参数设置为 true 时,pageNum<=0 时会查询第一页,
# pageNum>pages(超过总数时),会查询最后一页。默认false 时,直接根据参数进行查询。
pagehelper.reasonable=true
# 为了支持startPage(Object params)方法,增加了该参数来配置参数映射,
# 用于从对象中根据属性名取值, 可以配置 pageNum,pageSize,count,pageSizeZero,reasonable,
# 不配置映射的用默认值,
# 默认值为pageNum=pageNum;pageSize=pageSize;count=countSql;reasonable=reasonable;pageSizeZero=pageSizeZero。
pagehelper.params=pageNum=pageNum;pageSize=pageSize;count=countSql;reasonable=reasonable;pageSizeZero=pageSizeZero
# 支持通过 Mapper 接口参数来传递分页参数,默认值false,分页插件会从查询方法的参数值中,
# 自动根据上面 params 配置的字段中取值,查找到合适的值时就会自动分页。
pagehelper.support-methods-arguments=
# 当使用运行时动态数据源或没有设置 helperDialect 属性自动获取数据库类型时,
# 会自动获取一个数据库连接, 通过该属性来设置是否关闭获取的这个连接,默认true关闭,
# 设置为 false 后,不会关闭获取的连接,这个参数的设置要根据自己选择的数据源来决定。
pagehelper.close-conn=true
# 默认值为 false。设置为 true 时,允许在运行时根据多数据源自动识别对应方言的分页
# (不支持自动选择sqlserver2012,只能使用sqlserver)
pagehelper.auto-runtime-dialect=true

三、PageHelper.startPage 静态方法调用

除了 PageHelper.startPage 方法外,还提供了类似用法的 PageHelper.offsetPage 方法。

在你需要进行分页的 MyBatis 查询方法前调用 PageHelper.startPage 静态方法即可,紧跟在这个方法后的第一个MyBatis 查询方法会被进行分页。

public Page<Map<String,Object>> queryPage1(){
        //获取第1页,3条内容,默认查询总数count
        PageHelper.startPage(1, 3);
        //紧跟着的第一个select方法会被分页
        List<Map<String,Object>> list = userMapper.listUsers();
        // 分页时,实际返回的结果list类型是Page<E>,如果想取出分页信息,需要强制转换为Page<E>
        // 也可以這樣 PageInfo<Map<String, Object>> pageInfo = new PageInfo<Map<String, Object>>(list);
        Page<Map<String, Object>> pageInfo = (Page<Map<String, Object>>)list;
        return pageInfo;
}

public Page<Map<String,Object>> queryPage2(Map<String,Object> params){
    //参数包含pageNum=1 和 pageSize=10 都可以直接这样使用
    //支持 ServletRequest,Map,POJO 对象
    PageHelper.startPage(params);
    //紧跟着的第一个select方法会被分页
    List<Map<String,Object>> list = userMapper.listUsers();
    Page<Map<String, Object>> pageInfo = (Page<Map<String, Object>>)list;

    //后面的不会被分页,除非再次调用PageHelper.startPage
    List<Map<String,Object>> list2 = userMapper.listUsers();
    System.out.println("list2的大小是:" + list2.size());
    return pageInfo;
}

/**
 * 使用参数是安全的
 * 想要使用参数方式,需要配置 supportMethodsArguments 参数为 true,同时要配置 params 参数
 * 默认是pageSize和pageNum
 * 注:pageNum 和 pageSize 两个属性同时存在才会触发分页操作,在这个前提下,其他的分页参数才会生效。
 */
public Page<Map<String,Object>> queryPage3(Map<String,Object> params){
    List<Map<String,Object>> list = userMapper.listUsers(params);
    Page<Map<String, Object>> pageInfo = (Page<Map<String, Object>>)list;
    return pageInfo;
}

/**
 * 使用 ISelect 接口调用是安全的
 * ISelect 接口方式除了可以保证安全外,还特别实现了将查询转换为单纯的 count 查询方式,
 * 这个方法可以将任意的查询方法,变成一个 select count(*) 的查询方法。
 *
 */
public PageInfo<Map<String,Object>> queryPage4(){
    //分页,返回PageInfo分页对象
    PageInfo<Map<String,Object>> pageInfo = PageHelper.startPage(1, 10).doSelectPageInfo(new ISelect() {
        @Override
        public void doSelect() {
            userMapper.listUsers();
        }
    });

    //count查询,返回一个查询语句的count数
    long total = PageHelper.count(new ISelect() {
        @Override
        public void doSelect() {
            userMapper.listUsers();
        }
    });
    System.out.println("总记录数:" + total);
    return pageInfo;
}

四、什么时候会导致不安全的分页

上面在讲解的例子提到分页安全,什么时候会导致不安全的分页?

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>();
}

 

时刻与技术进步,每天一点滴,日久一大步!!! 本博客只为记录,用于学习,如有冒犯,请私信于我。