关于 PageHelper 分页插件的使用以及其中的坑

如何使用

直接导入依赖

<!--分页插件-->
<dependency>
  <groupId>com.github.pagehelper</groupId>
  <artifactId>pagehelper-spring-boot-starter</artifactId>
  <version>1.2.13</version>
</dependency>

常见的使用方式

public PageInfo findPage(int pageNum,int pageSize){
      PageHelper.startPage(pageNum,pageSize);
      List<User> list=userDao.selectAll();
      PageInfo pageInfo = new PageInfo(list);
      return pageInfo;
 }

说明

  1. startPage() 方法调用后必须紧跟 MyBatis 查询方法
  2. startPage() 方法是静态方法, 只需要传入 pageNumpageSize 两个参数
  3. 我们不需要对查询 SQL 做修改就可以达到分页的效果
  4. PageInfo 里面包含了一整个页面的信息
  5. PageInfo 构造函数传入的参数必须是 startPage() 后的那个方法得到的对象(原因下文解释)

PageInfo 中的参数说明

public class PageInfo<T> extends PageSerializable<T> {
    // 当前页的页码
    private int pageNum;
    // 页大小, 为分页时提供的参数
    private int pageSize;
    // 当前页的记录条数
    private int size;
    // 由第几条开始(数据库行号)
    private int startRow;
    // 第几条结束(数据库行号)
    private int endRow;
    // 一共有多少页
    private int pages;
    // 前一页的页号, 如果没有前一页就是为0
    private int prePage;
    // 后一页的页号, 如果没有前一页就是为0
    private int nextPage;
    // 是否是第一页
    private boolean isFirstPage;
    // 是否是最后一页
    private boolean isLastPage;
    // 是否有前一页
    private boolean hasPreviousPage;
    // 是否有后一页
    private boolean hasNextPage;
    // 每页显示的页码个数, 在构造方法里面被默认设置为8
    private int navigatePages;
    // 页码数
    private int[] navigatepageNums;
    // 首页
    private int navigateFirstPage;
    // 尾页
    private int navigateLastPage;

    // 方法省略
}

另外它还继承自 PageSerializable<T>

public class PageSerializable<T> implements Serializable {
    private static final long serialVersionUID = 1L;
    // 当前页的记录条数
    protected long total;
    // 当前页的每一条记录
    protected List<T> list;

    // 方法省略
}

PageHelper 使用中的坑

需求描述

出于安全性的考虑, 我想要查询到的 POJO 映射到 VO 类中,VO 类相比 POJO 类缺少了某些私密属性, 将 VO 作为基本元素封装到 PageInfo 中, 于是我写出来如下代码

PageInfo<PostItemVO> selectAll() {
    // 开启分页 (PageNum,PageSize)
    PageHelper.startPage(1, 2);
    List<Post> posts = postMapper.selectAll();
    // 属性拷贝
    List<PostItemVO> postItemVOS = new ArrayList<>();
    for (Post post : posts) {
        PostItemVO postItemVO = new PostItemVO();
        BeanUtils.copyProperties(post,postItemVO);
        postItemVOS.add(postItemVO);
    }
    // PageInfo 封装
    PageInfo<PostItemVO> pageInfo = new PageInfo<PostItemVO>(postItemVOS);
    return pageInfo;
}

Post 是我的 POJO, PostVO 是对应的 VO 类。数据库中查询得到 Post 集合,将其属性拷贝到 PostVO 集合。 乍一看其实写的不存在问题,但是我们在查询时就会出现问题 数据库表如下, 共有四条记录

得到的 PageInfo 如下

数据库中共有 4 条记录, 但是查询得到的 total 只有 2 条, 除此之外 nextPage,endRow…. 字段都出现了问题。

解决方法

方法一

先直接将查询结果封装到一个 PageInfo 中, 之后将将该 PageInfo 的 list 取出来做改造

PageInfo< selectAll() {
	PageHelper.startPage(1, 2);
	List<Post> posts = postMapper.selectAll();
	// 直接将Post封装
	PageInfo pageInfo = new PageInfo<>(posts);
	// 取出List进行数据转移
	ArrayList<PostItemVO> postItemVOS = new ArrayList<>();
	pageInfo.getList().forEach(post->{
		PostItemVO postItemVO = new PostItemVO();
		BeanUtils.copyProperties(post,postItemVO);
		postItemVOS.add(postItemVO);
	});
	pageInfo.setList(postItemVOS);
	return pageInfo;
}

查询结果如下

方法二 推荐

更加优雅的方式, 我们对查询语句进行改造, 使其直接返回一个 VO 集合,也就是说数据库表直接映射到 VO 上, 之后直接将该 List 封装在 PageInfo 之后, 直接返回就行了.

1 需要对 PostMapper.xml  进行修改

<!--结果映射-->
<resultMap id="ItemMap" type="im.yzh.mymyblog.model.vo.PostItemVO">
  <id column="id" jdbcType="BIGINT" property="id" />
  <result column="title" jdbcType="VARCHAR" property="title" />
  <result column="update_time" jdbcType="TIMESTAMP" property="updateTime" />
  <result column="published" jdbcType="BIT" property="published" />
  <result column="category_id" jdbcType="BIGINT" property="categoryId" />
</resultMap>
<!--一共有哪些字段-->
<sql id="Item_Column_List">
  id, title, update_time, published, category_id
</sql>
<!--查询SQL-->
<select id="selectAllItem" resultMap="ItemMap">
  select <include refid="Item_Column_List"/>
  from post order by update_time desc
</select>

2 在 PostMapper 接口中添加相关方法

List<PostItemVO> selectAllItem();

3 编写测试

PageInfo<PostItemVO> selectAllItem() {
    PageHelper.startPage(1, 2);
    List<PostItemVO> posts = postMapper.selectAllItem();
    PageInfo<PostItemVO> pageInfo = new PageInfo<PostItemVO>(posts);
    return pageInfo;
}

如果你有更好的解决方法欢迎留言评论。

原因分析

total 字段为例, 为什么正确的查询结果是 4, 但是用之前的错误查询方法的结果是 2。 先分析之前的错误查询方法的代码。 打个断点,我们发现,在 startPage() 调用之后, 随后的一个查询被进行了分页, 查询结果被封装为了一个 Page 对象:

查看 com.github.pagehelper.Page 源代码, Page 对象中包含了之后被封装到 PageInfo 中的信息:

也就是说, 我们的查询结果就已经包含了 PageInfo 之后需要的信息。查询得到的是一个 Page 实例,而不仅仅是一个 List, 所以说, 只对 posts进行 POJO 的拷贝是不够的, 这会丢失我们的分页信息。

对正确的查询过程进行调试分析:

1 被分页之后得到一个 Page 对象:

2 调用 PageInfo 构造函数会执行一个方法判断传入的参数是否为 Page 对象:

假如是之前的那种错误方法, 我们传入的是 List 对象, 那么 this.total就被初始化为了 List 的长度。 这就解释了为什么错误的方法得到的分页对象的 total 为 2

参考资料

如何使用分页插件

PageHelper,从pageinfo 中取到的total不正确的处理。(史上最最最最最最详细l!!!)_20fen的博客-CSDN博客_pagehelper total不对