SpringBoot整合Pagehelper分页插件
在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中我们用的是转义符号(&),但是在这里不用 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>(); }