MyBatis物理分页插件配置

wen java案例 2

MyBatis物理分页插件配置:从入门到性能优化实战指南

📚 目录导读

  1. 为什么必须用物理分页?逻辑分页的陷阱
  2. 主流物理分页插件选型对比
  3. PageHelper插件配置全解(含Spring Boot)
  4. MyBatis-Plus分页插件配置详解
  5. 自定义分页拦截器实现物理分页
  6. 常见配置陷阱与性能调优
  7. Q&A 高频问题解答

MyBatis物理分页插件配置

为什么必须用物理分页?逻辑分页的陷阱

在MyBatis开发中,分页是最常见的需求,许多初学者会使用List.subList()或内存分页,但这会引发严重性能问题:

  • 逻辑分页:一次性查询所有数据到内存,再截取部分,当数据量超过10万行时,内存可能瞬间溢出,数据库连接被长时间占用。
  • 物理分页:在SQL层面添加LIMITROWNUM,只查询当前页数据,内存和数据库负载可控。

性能对比:在100万条数据场景下,物理分页响应时间仅需毫秒级,而逻辑分页可能超过10秒,且内存消耗高达数百MB。

物理分页是生产环境的必要选择,而配置分页插件是实现物理分页最优雅的方式。


主流物理分页插件选型对比

目前Java生态中,MyBatis物理分页插件主要有以下几种:

插件名称 数据库支持 特点 适用场景
PageHelper MySQL、Oracle、PostgreSQL等12种 功能最全,自动方言识别 通用型项目,需复杂分页逻辑
MyBatis-Plus分页插件 MySQL、Oracle、SQL Server等 与MP深度整合,API简洁 使用MyBatis-Plus的项目
MyBatis-Pageable 主流数据库 轻量级,无冗余功能 微服务或轻量级项目
自定义拦截器 按需实现 灵活可控,无依赖 对分页有特殊定制需求

推荐:90%的场景选择PageHelper或MyBatis-Plus插件即可满足。


PageHelper插件配置全解(含Spring Boot)

1 Spring Boot整合(推荐方式)

<!-- pom.xml -->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <version>1.4.6</version>
</dependency>

application.yml中配置:

pagehelper:
  helper-dialect: mysql   # 自动识别(也可指定:mysql/oracle/postgresql)
  reasonable: true        # 合理化分页,页码超出范围时自动修正
  support-methods-arguments: true  # 支持通过Mapper参数传递Page对象
  params: count=countSql  # 自动生成COUNT查询
  auto-runtime-dialect: true  # 自动适配不同数据库(多数据源时开启)

2 传统XML配置(适用于非Spring Boot)

<!-- mybatis-config.xml -->
<plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor">
        <property name="helperDialect" value="mysql"/>
        <property name="reasonable" value="true"/>
        <property name="supportMethodsArguments" value="true"/>
    </plugin>
</plugins>

3 代码使用示例

// Service层
public PageInfo<User> getUsersByPage(int pageNum, int pageSize) {
    // 1. 设置分页参数(必须在查询之前)
    PageHelper.startPage(pageNum, pageSize);
    // 2. 执行查询(自动拦截并添加LIMIT)
    List<User> users = userMapper.selectAll();
    // 3. 封装分页结果(包含总记录数、页码等)
    return new PageInfo<>(users);
}
// Mapper接口无需修改,直接返回List
List<User> selectAll();

关键原理:PageHelper使用MyBatis的拦截器机制,在SQL执行前动态拼接LIMIT x OFFSET y,并自动生成COUNT查询。


MyBatis-Plus分页插件配置详解

1 配置步骤

<!-- 引入MP依赖后,需单独配置分页插件 -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.5</version>
</dependency>

Java配置类

@Configuration
public class MybatisPlusConfig {
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 添加分页拦截器,指定数据库类型
        PaginationInnerInterceptor paginationInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
        paginationInterceptor.setOverflow(true); // 溢出后自动跳转到首页
        paginationInterceptor.setMaxLimit(1000L); // 单页最大条数限制
        interceptor.addInnerInterceptor(paginationInterceptor);
        return interceptor;
    }
}

2 使用方式

// 使用MP的Page对象
public Page<User> getUserPage(int current, int size) {
    Page<User> page = new Page<>(current, size);
    // 条件构造器(可选)
    QueryWrapper<User> wrapper = new QueryWrapper<>();
    wrapper.eq("status", 1);
    // 分页查询,结果自动封装到page对象
    return userMapper.selectPage(page, wrapper);
}

注意事项:MP的分页插件与PageHelper不兼容,两者只能选其一。


自定义分页拦截器实现物理分页

当插件不满足需求时(如特殊分页逻辑、多租户分页),可自定义拦截器。

@Intercepts({
    @Signature(type = Executor.class, method = "query",
              args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
public class CustomPageInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object[] args = invocation.getArgs();
        MappedStatement ms = (MappedStatement) args[0];
        Object parameter = args[1];
        // 判断是否使用分页参数
        if (parameter instanceof PageParam) {
            PageParam pageParam = (PageParam) parameter;
            BoundSql boundSql = ms.getBoundSql(parameter);
            String originalSql = boundSql.getSql();
            // 拼接LIMIT语句
            String pageSql = originalSql + " LIMIT " + pageParam.getOffset() 
                           + "," + pageParam.getPageSize();
            // 替换SQL
            BoundSql newBoundSql = new BoundSql(
                ms.getConfiguration(), pageSql, 
                boundSql.getParameterMappings(), parameter);
            args[0] = new MappedStatement.Builder(ms.getConfiguration(), ms.getId(), 
                new SqlSource() {
                    @Override
                    public BoundSql getBoundSql(Object parameterObject) {
                        return newBoundSql;
                    }
                }, ms.getSqlCommandType()).build();
        }
        return invocation.proceed();
    }
}

优点:可根据业务自定义COUNT查询优化、动态条件拼接。缺点:需要处理数据库方言差异。


常见配置陷阱与性能调优

1 配置陷阱

  1. 多次调用PageHelper.startPage:每次查询前只调用一次,避免嵌套分页。

  2. 分页参数未传递到Mapper:确保support-methods-arguments=true,或通过Page对象传递。

  3. COUNT查询性能瓶颈:默认生成SELECT COUNT(1),若表数据量大,可手动优化:

    • 添加索引覆盖WHERE条件
    • 使用countSql参数指定自定义COUNT查询
  4. 多数据源下方言识别失败:设置auto-runtime-dialect=true,或手动指定helper-dialect

2 性能调优建议

  • 设置合理的pageSize上限:在插件中通过maxLimit限制,防止恶意请求。
  • 缓存COUNT结果:若数据更新不频繁,可用Redis缓存总记录数,避免重复查询。
  • 分页与排序结合:确保排序字段有索引,避免文件排序。
  • 避免大偏移量分页:深度分页(如第1000页)应改用游标或子查询优化。

Q&A 高频问题解答

Q1:PageHelper和MyBatis-Plus分页插件可以共存吗? A:不推荐,两者都基于拦截器,同时使用可能产生冲突,导致分页失效或SQL混乱,选用其一即可。

Q2:分页查询返回的总记录数不准确怎么办? A:检查是否有多表联查且包含GROUP BY,此时COUNT可能不准确,可手动编写COUNT查询,通过PageHelpercountSql参数指定。

Q3:如何实现不查询总记录数的快速分页(仅返回当前页)? A:PageHelper支持:PageHelper.startPage(pageNum, pageSize, false),第三个参数为false时跳过COUNT查询。

Q4:分页插件在Oracle数据库下报错,如何处理? A:Oracle使用ROWNUM方式分页,确认helper-dialect设置为oracle,若使用MyBatis-Plus,需设置DbType.ORACLE

Q5:自定义拦截器如何处理不同数据库方言? A:通过DatabaseIdProvider获取当前数据库类型,动态拼接不同的分页SQL(如MySQL用LIMIT,Oracle用ROWNUM嵌套)。

Q6:分页参数在Controller层如何传递? A:推荐使用Spring Boot的Pageable对象自动绑定:@RequestParam(defaultValue = "1") int page, @RequestParam(defaultValue = "10") int size,再通过PageHelper封装。

Q7:分页插件是否支持嵌套查询的分页? A:不支持,嵌套查询(如子查询中的分页)需要手动修改SQL,因为插件只拦截最外层查询。


物理分页插件配置是MyBatis性能优化的基础环节,选择PageHelper还是MyBatis-Plus插件,取决于项目技术栈和偏好,无论使用哪种,关键配置包括:数据库方言准确设置、合理性分页开启、COUNT查询优化,对于高并发场景,建议结合缓存和索引优化,并严格限制单页最大数量。

配置完成后,记得通过PageInfoPage对象获取分页元数据,避免手动计算偏移量,如果遇到分页失效或性能问题,优先检查拦截器加载顺序和SQL日志,这能快速定位问题根源。

抱歉,评论功能暂时关闭!