MyBatis分页

本文最后更新于:2 年前

引言

在现代Web开发中,有效的数据管理和呈现是至关重要的,尤其是当涉及到大量数据时。分页是一种常用的技术,用于提高数据处理的效率和用户界面的响应性。MyBatis作为一个强大的持久层框架,提供了多种分页实现方法,本文将探讨MyBatis中常见的分页技术,旨在帮助开发者根据应用需求选择最合适的分页解决方案。

手动实现分页

最基本的方法是在SQL查询中手动实现分页逻辑。这通常涉及到修改SQL语句以包含数据库特定的分页语法。

  • MySQL/PostgreSQL:使用LIMITOFFSET关键字。

    1
    SELECT * FROM users ORDER BY id LIMIT 10 OFFSET 20
  • Oracle:使用ROWNUM或在Oracle 12c及以上使用FETCH NEXT

    1
    SELECT * FROM ( SELECT a.*, ROWNUM rnum FROM (SELECT * FROM users ORDER BY id) a WHERE ROWNUM <= 30 ) WHERE rnum > 20
  • SQL Server:使用OFFSET-FETCH子句。

    1
    SELECT * FROM users ORDER BY id OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY

这种方法虽然直接且容易理解,但它要求开发者对不同数据库的分页机制有足够的了解,并且在数据库切换时需要修改SQL代码。

PageHelper

PageHelper是MyBatis社区提供的一个分页插件,这是一个非常流行的MyBatis分页插件,通过拦截器自动修改SQL实现分页,可以让开发者忽略手动编写分页SQL。

PageHelper支持多种数据库,无需担心不同数据库分页语法的差异。

DEMO

  1. 引入依赖

    1
    2
    3
    4
    5
    <dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.3.3</version>
    </dependency>
  2. 在MyBatis核心配置文件中添加拦截器

    1
    2
    3
    <plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
    </plugins>
  3. 在Mapper文件中定义查询方法

    1
    2
    3
    <select id="getAll" resultType="space.yangtao.domain.po.StudentPO">
    select * from tt_student order by id desc
    </select>
  4. 对应的接口方法为

    1
    List<StudentPO> getAll();
  5. 调用PageHelperstartPage方法,设置分页信息,同时将查询结果封装到一个PageInfo对象中

    1
    2
    3
    4
    5
    6
    7
    SqlSession session = SqlSessionUtil.getSession();
    StudentMapper studentMapper = session.getMapper(StudentMapper.class);
    int pageNum = 1; // 页码
    int pageSize = 2; // 每页记录数
    PageHelper.startPage(pageNum, pageSize);
    PageInfo<StudentPO> studentPOPageInfo = new PageInfo<>(studentMapper.getAll(), 10);
    log.info("PageHelper分页查询:{}", studentPOPageInfo);
  6. 运行结果如下

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    ...
    22:38:11.834 [main] DEBUG space.yangtao.mapper.StudentMapper.getAll_COUNT - ==> Preparing: SELECT count(0) FROM tt_student
    22:38:11.849 [main] DEBUG space.yangtao.mapper.StudentMapper.getAll_COUNT - ==> Parameters:
    22:38:11.861 [main] TRACE space.yangtao.mapper.StudentMapper.getAll_COUNT - <== Columns: count(0)
    22:38:11.861 [main] TRACE space.yangtao.mapper.StudentMapper.getAll_COUNT - <== Row: 3
    22:38:11.861 [main] DEBUG space.yangtao.mapper.StudentMapper.getAll_COUNT - <== Total: 1
    22:38:11.862 [main] DEBUG space.yangtao.mapper.StudentMapper - Cache Hit Ratio [space.yangtao.mapper.StudentMapper]: 0.0
    22:38:11.863 [main] DEBUG space.yangtao.mapper.StudentMapper.getAll - ==> Preparing: select * from tt_student order by id desc LIMIT ?, ?
    22:38:11.863 [main] DEBUG space.yangtao.mapper.StudentMapper.getAll - ==> Parameters: 2(Long), 2(Integer)
    22:38:11.864 [main] TRACE space.yangtao.mapper.StudentMapper.getAll - <== Columns: id, class_id, name, height, gender, birthday, create_time
    22:38:11.865 [main] TRACE space.yangtao.mapper.StudentMapper.getAll - <== Row: 1, 1, zhangsan, 180.0, 男, 1990-01-01, 2024-07-01 22:10:22
    22:38:11.866 [main] DEBUG space.yangtao.mapper.StudentMapper.getAll - <== Total: 1
    22:38:11.866 [main] INFO space.yangtao.client.StudentMapperTest - PageHelper分页查询:PageInfo{pageNum=2, pageSize=2, size=1, startRow=3, endRow=3, total=3, pages=2, list=Page{count=true, pageNum=2, pageSize=2, startRow=2, endRow=4, total=3, pages=2, reasonable=false, pageSizeZero=false}[StudentPO(id=1, name=zhangsan, height=180.0, gender=男, birthday=Mon Jan 01 00:00:00 CST 1990, createTime=Mon Jul 01 22:10:22 CST 2024)], prePage=1, nextPage=0, isFirstPage=false, isLastPage=true, hasPreviousPage=true, hasNextPage=false, navigatePages=8, navigateFirstPage=1, navigateLastPage=2, navigatepageNums=[1, 2]}
    ...

由日志可知,PageHelper已经帮我们自动设置了limit参数,同时看到返回的分页对象中除了需要的列表外,还含有其他一些可能用到的属性,如当前页、页大小、页码、总数、前一页、后一页、是否最后一页等。

RowBounds

MyBatis也提供了一个RowBounds类用于支持分页,通过在Mapper方法中使用RowBounds参数,可以进行物理分页。

使用示例:

1
List<StudentPO> studentPOPageInfo = studentMapper.getAll(new RowBounds(20, 10)); // 跳过前20条,然后取10条数据 

RowBounds的一个缺点是它是通过在内存中过滤记录实现的,即首先拉取所有记录然后在内存中进行分页,这在数据量较大时会影响性能。

Web环境下的选择

简单总结下以上各个分页方案:

  1. 手动实现
    • 优点
      • 控制性强:完全控制SQL查询,可以针对性地优化查询。
      • 灵活性高:可以根据具体需求调整SQL,满足复杂的分页需求。
    • 缺点
      • 代码复杂:需要手动编写用于计算总数的查询以及分页的查询,增加了开发的复杂性。
      • 维护成本高:每次分页逻辑调整或数据库迁移都需要手动修改SQL。
      • 性能问题:如果处理不当,可能会因为两次查询(一次获取总数,一次获取分页数据)而导致性能下降。
  2. PageHelper
    • 优点
      • 简单易用:通过简单的 API 调用实现分页,无需修改原有 SQL。
      • 自动化:自动处理总页数、总记录数的计算,对开发者透明。
      • 高效:通常只需要进行一次额外的查询来获取总记录数,且插件内部可以有优化措施。
    • 缺点
      • 依赖第三方:增加了对外部库的依赖,可能需要关注其兼容性和更新。
      • 适应性问题:虽然支持多种数据库,但在一些特殊的分页需求下可能需要调整默认行为。
  3. RowBounds
    • 优点
      • 内置支持:无需额外引入库或工具,MyBatis内置支持。
      • 简单使用:通过传递RowBounds对象为查询方法提供偏移量和限制即可实现分页。
    • 缺点
      • 效率低:RowBounds 是通过在数据库查询全部数据后,在内存中进行分页,这在数据量大时效率极低。
      • 不返回额外信息:不提供如总记录数等额外的分页信息,需要额外的查询和逻辑来实现。

总结

综合考虑Web环境下分页的需求,使用分页插件(如PageHelper)通常是最合适的选择,因为它既提供了开箱即用的分页功能,又能有效地返回必需的分页信息,同时保持了代码的简洁性和易维护性。如果对性能有极致要求,建议采用数据库自带的物理分页功能,并优化SQL查询。对于简单应用,RowBounds提供了快速实现分页的方法,尽管它在大数据量处理上效率不高。除此之外,使用分页时还需要限制每页的最大记录数、优化查询相关索引、同时充分利用缓存,以保证应用的稳定和高效。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!