【Mybatis】分页插件及其原理
原理:
- 调用 mapper 接口的方法前,用 PageHelper 的静态方法 start 设置分页参数
 - 分页参数会被存进 ThreadLocal 里面(这就是为什么要在调用 mapper 方法前设置参数的原因)。
 - 调用 mapper 接口方法后,会在执行 sql 前进行拦截,拦截器检测出 ThreadLocal 中的分页参数,改写 sql 并添加分页条件。
 - 新增一条 count(*)的 sql 语句执行
 - 将结果封装成 Page 对象
 - 后续可以利用 PageInfo 进行对 page 对象润色提供丰富的分页元数据
包含以下关键信息: 
- 
total:总记录数pages:总页数pageNum:当前页码pageSize:每页条数list:当前页的数据列表isFirstPage/isLastPage:是否为第一页 / 最后一页hasPreviousPage/hasNextPage:是否有上一页 / 下一页
 
补充:为什么没有看见 mapper 接口的实现类:
是因为MyBatis 会在运行时通过 jdk 的动态代理自动生成接口的实现类
@Servicepublic class UserServiceImpl implements UserService { @Autowired private UserMapper userMapper; @Override public PageInfo findAll(int pageNum, int pageSize) { // 开始分页,pageNum是页码,pageSize是每页条数 PageHelper.startPage(pageNum, pageSize); // 执行查询 List users = userMapper.findAll(); // 封装分页结果 return new PageInfo(users); } @Override public PageInfo findByCondition(String username, int pageNum, int pageSize) { // 开始分页 PageHelper.startPage(pageNum, pageSize); // 执行带条件的查询 List users = userMapper.findByCondition(username); // 封装分页结果 return new PageInfo(users); }}
额外补充:
users的特殊身份
当调用PageHelper.startPage()后,MyBatis 执行查询返回的users集合,实际上是com.github.pagehelper.Page类型的实例(Page是ArrayList的子类)。
这个Page对象内部已经包含了所有分页元数据:
- 
- 当前页码(
pageNum) - 每页条数(
pageSize) - 总记录数(
total) - 总页数(
pages)等 
 - 当前页码(
 
- 也就是说,分页查询的所有关键信息已经提前存储在
users对象中,并非PageInfo主动计算的。 PageInfo的工作方式PageInfo的构造方法会对传入的users进行判断:
- 
- 如果是
Page类型的集合,直接从中提取已有的分页元数据(pageNum、total等)。 - 基于这些元数据,自动计算出衍生信息:
 
 - 如果是
 
- 
- 
isFirstPage:pageNum == 1isLastPage:pageNum == pageshasPreviousPage:pageNum > 1hasNextPage:pageNum < pages
 
 - 
 
- 简单来说,
PageInfo只是将Page对象中已有的数据进行整理和封装,方便开发者使用,而不是重新计算分页信息。 - 元数据的来源
这些分页元数据(总记录数、当前页码等)是怎么来的? 
- 
pageNum和pageSize:来自PageHelper.startPage(pageNum, pageSize)的参数。total(总记录数):PageHelper 拦截器会自动执行一条COUNT(*)语句查询得到。pages(总页数):根据total和pageSize计算得出(pages = (total + pageSize - 1) / pageSize)。
 
这些数据会被 PageHelper 存入Page对象中,最终通过users集合传递给PageInfo。


