一、开篇引言

做 Java 后端项目,尤其是电商管理系统,分页查询是用户管理、商品列表、订单列表等所有列表接口的刚需功能。你是不是也遇到过这些问题:
MyBatis-Plus 分页查询,total总条数始终为 0,分页完全不生效
分页参数传了,但返回的还是全量数据,没有分页效果
多条件查询 + 分页时,条件拼接混乱,分页逻辑错乱
升级 MyBatis-Plus 版本后,旧版分页配置突然失效
本文一次性讲透 MyBatis-Plus 分页查询的核心原理、5 个高频踩坑、完整解决方案,所有代码 100% 可直接复制,10 分钟解决所有分页问题,完美适配你正在做的电商管理系统。本文基于 SpringBoot 2.7.x + MyBatis-Plus 3.5.3,适合 Java 后端初学者、正在做前后端分离项目的同学,尤其适合大二计算机专业学生练手项目。

二、核心知识点(先懂原理,再避坑)

1. MyBatis-Plus 分页实现原理
MyBatis-Plus 的分页功能,核心是通过分页拦截器PaginationInnerInterceptor 自动拦截 SQL,在原 SQL 基础上拼接LIMIT语句实现分页,同时自动查询总条数total。
低版本(3.4.0 之前):使用PaginationInterceptor旧版拦截器
高版本(3.4.0 及以上):使用MybatisPlusInterceptor+PaginationInnerInterceptor新版拦截器(本文基于新版)
核心逻辑:拦截器自动处理分页,无需手动编写分页 SQL,只需要传入Page对象即可
2. 分页查询核心要素
Page对象:封装分页参数(当前页current、每页条数size),以及分页结果(总条数total、总页数pages、数据列表records)
QueryWrapper/LambdaQueryWrapper:封装查询条件,实现多条件分页查询
分页拦截器:必须配置,否则分页完全不生效(90% 的坑都出在这里)

三、完整分页实现代码(直接复制到项目)

1. 环境准备(pom.xml 依赖)

<!-- SpringBoot Web核心依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

<!-- MyBatis-Plus 核心依赖(3.5.3稳定版) -->
<dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.3</version>
</dependency>

<!-- MySQL驱动 -->
<dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <scope>runtime</scope>
</dependency>

2. 关键:分页拦截器配置(90% 的坑都在这里)
创建配置类MyBatisPlusConfig.java,必须配置,否则分页不生效

package com.example.demo.config;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

/**
 * MyBatis-Plus配置类
 * 核心:开启分页拦截器,解决分页失效问题
 */
@Configuration
public class MyBatisPlusConfig {

    /**
     * 配置MyBatis-Plus拦截器
     */
    @Bean
    public MybatisPlusInterceptor mybatisPlusInterceptor() {
        MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
        // 1. 添加分页拦截器,指定数据库类型为MySQL(必须指定,否则分页不生效)
        interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
        // 2. 可选:添加乐观锁拦截器、多租户拦截器等
        return interceptor;
    }
}

3. 分页查询接口实现(以用户管理为例)

/**
 * 分页+多条件查询用户
 * @param current 当前页,默认1
 * @param size 每页条数,默认10
 * @param username 用户名(模糊搜索)
 * @param phone 手机号(模糊搜索)
 * @return 分页结果
 */
@GetMapping("/user/page")
public Result<Page<User>> pageUser(
        @RequestParam(defaultValue = "1") Integer current,
        @RequestParam(defaultValue = "10") Integer size,
        @RequestParam(required = false) String username,
        @RequestParam(required = false) String phone) {

    // 1. 创建分页对象(必须new Page<>(current, size),不能传null)
    Page<User> page = new Page<>(current, size);

    // 2. 构建条件查询Wrapper
    LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
    // 用户名模糊查询(非空才拼接,避免无效条件)
    if (StrUtil.isNotBlank(username)) {
        wrapper.like(User::getUsername, username);
    }
    // 手机号模糊查询
    if (StrUtil.isNotBlank(phone)) {
        wrapper.like(User::getPhone, phone);
    }
    // 按创建时间倒序排序,最新数据排在前面
    wrapper.orderByDesc(User::getCreateTime);

    // 3. 执行分页查询(调用MyBatis-Plus内置page方法)
    Page<User> userPage = userService.page(page, wrapper);

    return Result.success(userPage);
}

四、5 个高频踩坑 + 完整解决方案(收藏率核心)

坑 1:分页查询 total 总条数为 0,分页完全不生效
原因:

没有配置分页拦截器PaginationInnerInterceptor(最常见)
配置了旧版PaginationInterceptor,与高版本 MyBatis-Plus 不兼容
分页拦截器中没有指定数据库类型DbType.MYSQL
解决方案:
严格按照本文的MyBatisPlusConfig配置,使用新版MybatisPlusInterceptor+PaginationInnerInterceptor
必须指定DbType.MYSQL,否则拦截器无法识别数据库,不执行分页逻辑
彻底删除旧版PaginationInterceptor配置,避免冲突
坑 2:分页参数传了,但返回全量数据,没有分页效果
原因:

解决方案:
优先使用 MyBatis-Plus 内置的page()方法,自动处理分页
current参数必须≥1,size参数必须≥1,否则分页不生效
自定义 SQL 分页时,必须将Page对象作为第一个入参,MyBatis-Plus 会自动拦截
坑 3:多条件查询 + 分页时,条件拼接混乱,分页逻辑错乱
原因:

没有对查询参数做非空判断,空参数也拼接了like/eq条件,导致查询结果为空
条件顺序错误,排序条件放在了条件之前,影响分页逻辑
使用了and()/or()嵌套条件,没有正确处理括号逻辑
解决方案:
所有条件拼接前,必须做非空判断(如StrUtil.isNotBlank(username)),避免无效条件
排序条件orderByDesc必须放在所有查询条件之后
复杂条件使用lambda链式写法,避免括号逻辑错误,示例:

wrapper.like(User::getUsername, username)
       .like(User::getPhone, phone)
       .eq(User::getStatus, 1)
       .orderByDesc(User::getCreateTime);

坑 4:升级 MyBatis-Plus 版本后,分页配置突然失效
原因:

MyBatis-Plus 3.4.0 版本对分页拦截器做了重构,旧版PaginationInterceptor被废弃
高版本中,PaginationInnerInterceptor必须手动指定数据库类型,否则不生效
依赖版本冲突,MyBatis-Plus 与 SpringBoot 版本不兼容
解决方案:
升级到 3.5.3 稳定版,彻底替换为新版分页拦截器配置
确保 SpringBoot 版本与 MyBatis-Plus 版本兼容(SpringBoot 2.7.x 适配 MyBatis-Plus 3.5.x)
检查pom.xml中 MyBatis-Plus 依赖版本,统一版本号,避免多版本冲突
坑 5:分页查询性能差,大数据量下分页缓慢
原因:
没有对查询条件加索引,导致全表扫描,分页查询耗时久
分页参数size过大,一次性查询大量数据
没有做分页优化,深分页(如current=1000)时性能极差
解决方案:
给查询条件字段(如username、phone、create_time)添加索引,提升查询速度
限制size最大值(如最大 100 条 / 页),避免一次性查询大量数据
深分页优化:使用id游标分页,替代LIMIT offset, size,提升性能

// 游标分页优化示例
if (current == 1) {
    wrapper.orderByDesc(User::getId);
} else {
    // 上次查询的最大id,直接从id之后查询,避免offset
    wrapper.lt(User::getId, lastMaxId).orderByDesc(User::getId);
}

五、接口测试效果演示

请求示例
GET /user/page?current=1&size=10&username=张&phone=138
成功返回结果

{
    "code": 200,
    "msg": "操作成功",
    "data": {
        "records": [
            {
                "id": 1,
                "username": "zhangsan",
                "phone": "13800138000",
                "createTime": "2026-04-10T10:00:00"
            }
        ],
        "total": 1,
        "size": 10,
        "current": 1,
        "pages": 1
    }
}

六、总结与延伸

本文完整讲解了 MyBatis-Plus 分页查询的核心原理、完整实现、5 个高频踩坑与解决方案,完美适配电商管理系统的用户、商品、订单等所有列表接口。通过本文的实现,你可以:
彻底解决分页失效问题,实现稳定的分页查询
掌握 MyBatis-Plus 分页的核心用法,避免踩坑
优化分页性能,适配大数据量场景
为电商系统的所有列表接口提供统一的分页规范
本文是电商管理系统实战系列的第 2 篇,上一篇讲解了用户管理模块 CRUD 实现,下一篇将讲解 SpringBoot 集成 JWT 实现登录认证,感兴趣的同学可以关注我,持续更新 Java 后端实战内容。
七、互动引导)
你在使用 MyBatis-Plus 分页时,还遇到过哪些问题?欢迎在评论区留言,我会逐一解答!觉得有用的同学,点赞 + 收藏不迷路,后续更新更多 Java 后端学习、项目实战、面试技巧!关注我,持续分享 Java 后端干货,陪你从大二到秋招拿 offer!

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐