【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 的动态代理自动生成接口的实现类
@Service
public class UserServiceImpl implements UserService {@Autowiredprivate UserMapper userMapper;@Overridepublic PageInfo<User> findAll(int pageNum, int pageSize) {// 开始分页,pageNum是页码,pageSize是每页条数PageHelper.startPage(pageNum, pageSize);// 执行查询List<User> users = userMapper.findAll();// 封装分页结果return new PageInfo<>(users);}@Overridepublic PageInfo<User> findByCondition(String username, int pageNum, int pageSize) {// 开始分页PageHelper.startPage(pageNum, pageSize);// 执行带条件的查询List<User> 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 == 1
isLastPage
:pageNum == pages
hasPreviousPage
:pageNum > 1
hasNextPage
: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
。