当前位置: 首页 > news >正文

从基础到进阶:MyBatis-Plus 分页查询封神指南

在 Java 后端开发中,分页查询是高频需求 —— 无论是用户列表、订单记录还是数据分析,都需要通过分页来限制数据量,提升接口响应速度和用户体验。传统的 MyBatis 实现分页需要手动编写LIMIT语句、计算总条数,代码冗余且容易出错。而 MyBatis-Plus(简称 MP)的分页功能,能让这一切变得简单:一行配置、一行代码,就能完成分页查询,甚至支持复杂条件、自定义 SQL、联表查询等场景。

本文将从基础用法到高级技巧,全方位解析 MyBatis-Plus 的分页功能,结合 20 + 实战案例,让你彻底掌握如何用 MP 写出简洁高效的分页代码,从此告别分页查询的繁琐操作。

一、为什么说传统分页是 “体力活”?

在介绍 MyBatis-Plus 的分页之前,我们先回顾一下传统 MyBatis 实现分页的流程。假设要查询 “第 2 页的用户列表,每页 10 条,按创建时间倒序”,你需要做这些事:

  1. 编写 Mapper 接口:定义两个方法,一个查列表,一个查总条数;
  2. 编写 XML 映射:手动拼接LIMIT语句(LIMIT 10 OFFSET 10),总条数查询用COUNT(*)
  3. 处理分页参数:在 Service 层计算offset = (pageNum - 1) * pageSize
  4. 封装分页结果:将 “列表数据 + 总条数 + 页码 + 页大小” 封装到分页对象中。

传统 MyBatis 分页代码示例

java

// 1. Mapper接口
public interface UserMapper {// 查询分页列表List<User> selectUserPage(@Param("offset") int offset, @Param("pageSize") int pageSize);// 查询总条数int selectUserCount();
}// 2. XML映射文件
<select id="selectUserPage" resultType="User">SELECT * FROM user ORDER BY create_time DESC LIMIT #{offset}, #{pageSize}
</select>
<select id="selectUserCount" resultType="int">SELECT COUNT(*) FROM user
</select>// 3. Service层调用
public PageResult<User> getUserPage(int pageNum, int pageSize) {int offset = (pageNum - 1) * pageSize;List<User> records = userMapper.selectUserPage(offset, pageSize);int total = userMapper.selectUserCount();return new PageResult<>(records, total, pageNum, pageSize);
}

这套流程看似简单,但存在诸多问题:

  • 代码冗余:每个分页查询都要写两个方法(列表 + 总数),重复劳动;
  • 容易出错offset计算错误(如pageNum=0导致负数)、LIMIT语法写错;
  • 扩展性差:添加查询条件时,需要同时修改列表和总数的 SQL;
  • 不支持复杂场景:联表查询、自定义排序的分页实现更复杂。

而 MyBatis-Plus 的分页功能,正是为解决这些痛点而生 —— 通过插件化思想,自动拦截 SQL 并添加分页逻辑,开发者只需关注 “查询条件”,无需手动处理分页细节。

二、MyBatis-Plus 分页基础:3 步实现分页查询

MyBatis-Plus 的分页功能基于PaginationInnerInterceptor插件,核心原理是拦截 SQL 语句,自动添加分页条件(如LIMIT)和总条数查询。使用前需完成 3 步:引入依赖、配置插件、编写代码。

2.1 第一步:引入依赖

如果项目已集成 MyBatis-Plus,无需额外引入依赖(MP 的核心包已包含分页功能)。若未集成,需在pom.xml中添加:

xml

<!-- MyBatis-Plus核心依赖 -->
<dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.3.1</version> <!-- 建议使用最新版本 -->
</dependency>

注意:MyBatis-Plus 3.4.0 + 版本的分页插件有调整,本文基于 3.5.x 版本讲解,与旧版本略有差异。

2.2 第二步:配置分页插件

MyBatis-Plus 的分页功能需要通过MybatisPlusInterceptor注册PaginationInnerInterceptor插件。在 Spring Boot 项目中,创建配置类:

java

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;@Configuration
public class MyBatisPlusConfig {/*** 注册分页插件*/@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();// 添加分页插件PaginationInnerInterceptor paginationInterceptor = new PaginationInnerInterceptor();// 设置数据库类型(根据实际使用的数据库调整)paginationInterceptor.setDbType(DbType.MYSQL);// 溢出总页数后是否进行处理(默认false,即返回最后一页)paginationInterceptor.setOverflow(true);interceptor.addInnerInterceptor(paginationInterceptor);return interceptor;}
}

关键参数说明

  • setDbType(DbType.MYSQL):指定数据库类型,MP 会根据数据库类型生成对应的分页 SQL(如 MySQL 用LIMIT,Oracle 用ROWNUM);
  • setOverflow(true):当pageNum超过总页数时,返回最后一页数据(而非空),避免前端报错;
  • 若需要同时使用乐观锁、多租户等插件,只需在MybatisPlusInterceptor中继续添加即可(插件执行顺序有讲究,分页插件建议放最后)。

2.3 第三步:编写分页查询代码

配置完成后,分页查询的核心是Page对象 —— 通过它传递分页参数(页码、页大小),并接收分页结果(数据列表、总条数等)。

2.3.1 基础分页(无查询条件)

以查询用户列表为例,完整流程如下:

  1. 定义实体类

    java

    @Data
    @TableName("user") // 对应数据库表名
    public class User {private Long id;private String name;private Integer age;private String email;private LocalDateTime createTime;
    }
    
  2. 编写 Mapper 接口

    java

    import com.baomidou.mybatisplus.core.mapper.BaseMapper;
    import com.baomidou.mybatisplus.core.metadata.IPage;
    import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
    import org.apache.ibatis.annotations.Mapper;@Mapper
    public interface UserMapper extends BaseMapper<User> {// 继承BaseMapper后,无需手动定义方法,直接使用父类的selectPage方法
    }
    
  3. Service 层实现

    java

    import com.baomidou.mybatisplus.core.metadata.IPage;
    import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
    import org.springframework.stereotype.Service;@Service
    public class UserService {private final UserMapper userMapper;// 构造器注入(推荐)public UserService(UserMapper userMapper) {this.userMapper = userMapper;}/*** 基础分页查询* @param pageNum 页码(从1开始)* @param pageSize 每页条数* @return 分页结果*/public IPage<User> getUserPage(Integer pageNum, Integer pageSize) {// 1. 创建Page对象,传入分页参数Page<User> page = new Page<>(pageNum, pageSize);// 2. 调用BaseMapper的selectPage方法,自动分页// 第一个参数:Page对象(用于传递参数和接收结果)// 第二个参数:查询条件(null表示无条件)return userMapper.selectPage(page, null);}
    }
    
  4. Controller 层接收并返回结果

    java

    import com.baomidou.mybatisplus.core.metadata.IPage;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.bind.annotation.RestController;@RestController
    public class UserController {private final UserService userService;public UserController(UserService userService) {this.userService = userService;}@GetMapping("/users")public IPage<User> getUsers(@RequestParam(defaultValue = "1") Integer pageNum,@RequestParam(defaultValue = "10") Integer pageSize) {return userService.getUserPage(pageNum, pageSize);}
    }
    
  5. 测试结果
    访问http://localhost:8080/users?pageNum=1&pageSize=10,返回 JSON 格式的分页结果:

    json

    {"records": [{"id": 1, "name": "张三", "age": 20, "email": "zhangsan@example.com", "createTime": "2023-01-01T00:00:00"},// ... 更多用户数据],"total": 100, // 总条数"size": 10,  // 每页条数"current": 1, // 当前页码"pages": 10,  // 总页数"hasNext": true, // 是否有下一页"hasPrevious": false // 是否有上一页
    }
    

    IPage接口(Page是其实现类)包含了分页所需的所有信息,无需手动封装,直接返回给前端即可。

2.3.2 带条件的分页查询

实际开发中,分页往往需要结合查询条件(如按年龄筛选、按创建时间排序)。此时只需在selectPage方法中传入QueryWrapper对象即可。

示例:查询年龄大于 18 且邮箱不为空的用户,按创建时间倒序

java

public IPage<User> getUserByCondition(Integer pageNum, Integer pageSize, Integer minAge, String email
) {Page<User> page = new Page<>(pageNum, pageSize);// 构建查询条件QueryWrapper<User> queryWrapper = new QueryWrapper<>();// 年龄大于minAge(若minAge不为null)if (minAge != null) {queryWrapper.gt("age", minAge);}// 邮箱不为空queryWrapper.isNotNull("email");// 按创建时间倒序排序queryWrapper.orderByDesc("create_time");// 执行分页查询return userMapper.selectPage(page, queryWrapper);
}

Controller 层调用

java

@GetMapping("/users/condition")
public IPage<User> getUsersByCondition(@RequestParam(defaultValue = "1") Integer pageNum,@RequestParam(defaultValue = "10") Integer pageSize,@RequestParam(required = false) Integer minAge,@RequestParam(required = false) String email
) {return userService.getUserByCondition(pageNum, pageSize, minAge, email);
}

生成的 SQL 解析
MP 会自动拦截查询,生成如下 SQL(以 MySQL 为例):

sql

-- 查询数据列表(带条件和排序)
SELECT id,name,age,email,create_time 
FROM user 
WHERE age > 18 AND email IS NOT NULL 
ORDER BY create_time DESC 
LIMIT 0,10;-- 自动查询总条数(条件与列表查询一致)
SELECT COUNT(*) 
FROM user 
WHERE age > 18 AND email IS NOT NULL;

可以看到,条件和排序会同时应用到 “数据查询” 和 “总条数查询” 中,无需手动编写两条 SQL,大幅减少冗余。

三、进阶用法:自定义 SQL 的分页查询

虽然 MP 的QueryWrapper能满足大部分条件查询,但复杂场景(如联表查询、子查询)仍需自定义 SQL。此时只需在 Mapper 接口中传入IPage参数,MP 会自动为自定义 SQL 添加分页逻辑。

3.1 XML 方式自定义 SQL 分页

场景:查询用户及其关联的角色信息(联表查询useruser_role表),实现分页。

3.1.1 定义 VO(返回结果封装)

java

@Data
public class UserRoleVO {private Long userId;private String userName;private Integer age;private String roleName; // 角色名称private LocalDateTime createTime;
}
3.1.2 Mapper 接口定义

java

@Mapper
public interface UserMapper extends BaseMapper<User> {/*** 自定义分页查询(联表查询用户和角色)* @param page 分页参数* @param roleName 角色名称(查询条件)* @return 分页结果*/IPage<UserRoleVO> selectUserRolePage(Page<UserRoleVO> page, @Param("roleName") String roleName);
}

关键:方法第一个参数必须是Page对象(用于接收分页参数和结果),后续参数为查询条件(需用@Param指定参数名)。

3.1.3 XML 映射文件编写

xml

<mapper namespace="com.example.mapper.UserMapper"><!-- 自定义分页查询SQL --><select id="selectUserRolePage" resultType="com.example.vo.UserRoleVO">SELECT u.id AS userId,u.name AS userName,u.age,r.name AS roleName,u.create_time AS createTimeFROM user uLEFT JOIN user_role ur ON u.id = ur.user_idLEFT JOIN role r ON ur.role_id = r.id<!-- 动态条件:角色名称模糊查询 --><where><if test="roleName != null and roleName != ''">r.name LIKE CONCAT('%', #{roleName}, '%')</if></where><!-- 排序 -->ORDER BY u.create_time DESC</select>
</mapper>

注意:XML 中无需手动添加LIMIT语句,MP 会自动拦截 SQL 并添加分页条件(根据数据库类型)。

3.1.4 Service 层调用

java

public IPage<UserRoleVO> getUserRolePage(Integer pageNum, Integer pageSize, String roleName
) {Page<UserRoleVO> page = new Page<>(pageNum, pageSize);return userMapper.selectUserRolePage(page, roleName);
}
3.1.5 生成的 SQL 解析

当调用selectUserRolePage时,MP 会自动生成两条 SQL:

  1. 数据查询 SQL(添加LIMIT):

    sql

    SELECT u.id AS userId, u.name AS userName, u.age, r.name AS roleName, u.create_time AS createTime
    FROM user u
    LEFT JOIN user_role ur ON u.id = ur.user_id
    LEFT JOIN role r ON ur.role_id = r.id
    WHERE r.name LIKE CONCAT('%', '管理员', '%')
    ORDER BY u.create_time DESC
    LIMIT 0, 10;
    
  2. 总条数查询 SQL(自动生成COUNT(*)):

    sql

    SELECT COUNT(*) 
    FROM user u
    LEFT JOIN user_role ur ON u.id = ur.user_id
    LEFT JOIN role r ON ur.role_id = r.id
    WHERE r.name LIKE CONCAT('%', '管理员', '%');
    

    这意味着,即使是自定义 SQL,MP 也能自动处理分页逻辑,开发者只需关注核心查询逻辑即可。

3.2 注解方式自定义 SQL 分页

除了 XML,也可以用@Select注解编写自定义 SQL,适合简单的查询场景。

示例:用注解实现用户 - 角色联表分页查询

java

@Mapper
public interface UserMapper extends BaseMapper<User> {/*** 注解方式自定义分页查询*/@Select("SELECT " +"u.id AS userId, u.name AS userName, u.age, " +"r.name AS roleName, u.create_time AS createTime " +"FROM user u " +"LEFT JOIN user_role ur ON u.id = ur.user_id " +"LEFT JOIN role r ON ur.role_id = r.id " +"WHERE r.name LIKE CONCAT('%', #{roleName}, '%') " +"ORDER BY u.create_time DESC")IPage<UserRoleVO> selectUserRolePageByAnnotation(Page<UserRoleVO> page, @Param("roleName") String roleName);
}

用法与 XML 方式一致,Service 层直接调用即可。对于复杂 SQL,建议优先使用 XML 方式(可读性更好)。

四、高级特性:让分页查询更灵活

MyBatis-Plus 的分页功能远不止基础查询,还支持结果自定义、分页插件细粒度配置、逻辑删除分页等高级特性,满足复杂业务场景。

4.1 分页结果自定义(封装 VO)

默认的IPage结果包含recordstotal等字段,但若前端需要特定格式(如datatotalCount),可以手动封装分页结果。

示例:自定义分页响应 VO

java

@Data
public class PageResponse<T> {private int code = 200; // 状态码private String message = "success"; // 提示信息private long total; // 总条数private int pageNum; // 当前页码private int pageSize; // 每页条数private List<T> data; // 数据列表// 构造方法:从IPage转换public PageResponse(IPage<T> page) {this.total = page.getTotal();this.pageNum = (int) page.getCurrent();this.pageSize = (int) page.getSize();this.data = page.getRecords();}// 静态工厂方法(更简洁)public static <T> PageResponse<T> of(IPage<T> page) {return new PageResponse<>(page);}
}

Service 层调用

java

public PageResponse<UserRoleVO> getUserRolePageCustom(Integer pageNum, Integer pageSize, String roleName
) {Page<UserRoleVO> page = new Page<>(pageNum, pageSize);IPage<UserRoleVO> resultPage = userMapper.selectUserRolePage(page, roleName);return PageResponse.of(resultPage);
}

返回给前端的结果更符合业务规范:

json

{"code": 200,"message": "success","total": 50,"pageNum": 1,"pageSize": 10,"data": [/* 用户角色数据 */]
}

4.2 分页插件的细粒度配置

全局配置分页插件后,若某些方法需要特殊配置(如不同的页大小限制、溢出处理策略),可以通过Page对象的方法动态调整。

4.2.1 单个查询设置最大页大小

防止恶意请求(如pageSize=10000导致性能问题),可以在Page对象中设置最大页大小:

java

public IPage<User> getUserWithMaxSize(Integer pageNum, Integer pageSize) {// 设置最大页大小为100,若传入的pageSize>100,则强制使用100Page<User> page = new Page<>(pageNum, pageSize).setSearchCount(true) // 是否查询总条数(默认true,false则不查total).setMaxLimit(100L); // 最大页大小限制return userMapper.selectPage(page, null);
}
4.2.2 关闭总条数查询(提升性能)

某些场景下(如滚动加载、只需要数据列表),可以关闭总条数查询(setSearchCount(false)),减少一次 SQL 执行,提升性能:

java

public IPage<User> getUserWithoutTotal(Integer pageNum, Integer pageSize) {Page<User> page = new Page<>(pageNum, pageSize);page.setSearchCount(false); // 不查询总条数(total=0,pages=0)return userMapper.selectPage(page, null);
}

生成的 SQL 只会有数据查询(无COUNT(*)查询):

sql

SELECT id,name,age,email,create_time FROM user LIMIT 0,10;

4.3 结合 Lambda 表达式的类型安全查询

QueryWrapper虽然灵活,但字符串列名(如"age")容易写错(编译期不报错,运行期才发现)。MP 的LambdaQueryWrapper通过 Lambda 表达式引用实体类字段,实现类型安全的查询。

示例:用 LambdaQueryWrapper 实现条件分页

java

public IPage<User> getUserByLambda(Integer pageNum, Integer pageSize, Integer age) {Page<User> page = new Page<>(pageNum, pageSize);// 用LambdaQueryWrapper避免硬编码列名LambdaQueryWrapper<User> lambdaWrapper = new LambdaQueryWrapper<>();if (age != null) {lambdaWrapper.gt(User::getAge, age); // 引用User类的age字段(编译期检查)}lambdaWrapper.isNotNull(User::getEmail) // 等价于email IS NOT NULL.orderByDesc(User::getCreateTime); // 按createTime倒序return userMapper.selectPage(page, lambdaWrapper);
}

优势:若实体类字段名修改(如age改为userAge),编译器会直接报错,避免线上问题。

4.4 逻辑删除的分页查询

MyBatis-Plus 的逻辑删除(通过@TableLogic注解)会自动在查询中添加deleted=0条件,分页查询也会自动适配,无需额外处理。

示例

  1. 实体类添加逻辑删除字段

    java

    @Data
    @TableName("user")
    public class User {private Long id;private String name;private Integer age;// 逻辑删除字段(0=未删除,1=已删除)@TableLogicprivate Integer deleted;
    }
    
  2. 分页查询

    java

    public IPage<User> getDeletedUser(Integer pageNum, Integer pageSize) {Page<User> page = new Page<>(pageNum, pageSize);// 查询已删除的用户(手动添加deleted=1条件)QueryWrapper<User> queryWrapper = new QueryWrapper<>();queryWrapper.eq("deleted", 1);return userMapper.selectPage(page, queryWrapper);
    }
    

    若不添加eq("deleted", 1),默认查询的是deleted=0的未删除数据(逻辑删除的全局配置生效)。分页查询会自动包含逻辑删除条件,与普通查询一致。

4.5 多表联表分页的性能优化

联表分页查询(尤其是多表 JOIN)容易出现性能问题,可通过以下方式优化:

  1. 确保关联字段有索引:如user_role.user_idrole.id需建立索引;
  2. ** 避免 SELECT ***:只查询需要的字段,减少数据传输;
  3. 子查询优化:将复杂 JOIN 改为子查询,减少关联表数量;
  4. 分页参数合理化:限制最大pageSize(如不超过 1000),避免一次查询过多数据。

示例:优化后的联表分页查询

xml

<select id="selectUserRolePageOptimized" resultType="com.example.vo.UserRoleVO">SELECT u.id AS userId,u.name AS userName,u.age,(SELECT r.name FROM role r WHERE r.id = ur.role_id) AS roleName,u.create_time AS createTimeFROM user uLEFT JOIN user_role ur ON u.id = ur.user_id<where><if test="roleName != null">EXISTS (SELECT 1 FROM role r WHERE r.id = ur.role_id AND r.name LIKE CONCAT('%', #{roleName}, '%'))</if></where>ORDER BY u.create_time DESC
</select>

通过子查询和EXISTS替代多表 JOIN,减少关联次数,提升分页查询效率。

五、避坑指南:分页查询常见问题及解决方案

在使用 MyBatis-Plus 分页功能时,可能会遇到一些 “坑”,这里总结了最常见的问题及解决方案。

5.1 分页插件不生效(查询所有数据,无分页)

现象:调用selectPage后,返回所有数据(total等于总条数,但records包含全部数据,未分页)。

可能原因及解决

  1. 未配置分页插件:检查是否在MybatisPlusInterceptor中添加了PaginationInnerInterceptor
  2. 插件顺序错误:若同时配置了其他插件(如IllegalSQLInnerInterceptor),分页插件可能被覆盖,建议分页插件放最后;
  3. Mapper 接口未继承 BaseMapper:自定义 Mapper 接口需继承BaseMapper,或手动定义selectPage方法;
  4. Page 对象未作为第一个参数:自定义方法中,Page对象必须是第一个参数,否则 MP 无法识别。

5.2 总条数查询错误(total 与实际不符)

现象:分页结果的total值与实际总条数不符(如实际 100 条,返回 50 条)。

可能原因及解决

  1. 查询条件不一致:自定义 SQL 中,数据查询和总条数查询的条件不一致(MP 会自动复用 WHERE 条件,若手动写 COUNT 则可能出错);
    • 解决方案:避免手动编写 COUNT 语句,让 MP 自动生成;
  2. 逻辑删除配置错误:若实体类有逻辑删除字段但未配置@TableLogic,总条数会包含已删除数据;
    • 解决方案:添加@TableLogic注解,或在条件中手动排除已删除数据。

5.3 分页查询性能差(耗时过长)

现象:分页查询耗时超过 1 秒,甚至超时。

优化方案

  1. 添加索引:确保 WHERE 条件、ORDER BY 的字段有索引(如agecreate_time);
  2. 避免全表扫描:检查 SQL 是否走索引(用EXPLAIN分析),避免SELECT *
  3. 限制最大页大小:通过setMaxLimit(1000)防止pageSize过大;
  4. 关闭总条数查询:非必要时用setSearchCount(false)减少一次 SQL;
  5. 分库分表场景:若数据量超千万级,建议结合分库分表中间件(如 ShardingSphere),MP 分页只作用于单表。

5.4 多数据源场景下分页插件不生效

现象:项目使用多数据源(如 dynamic-datasource-spring-boot-starter),部分数据源分页不生效。

解决方案
分页插件需在每个数据源的 MyBatis 配置中单独注册,或使用全局配置(确保插件被所有数据源共享)。

示例(多数据源配置分页插件):

java

@Configuration
public class MultiDataSourceConfig {@Beanpublic MybatisPlusInterceptor mybatisPlusInterceptor() {MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));return interceptor;}// 数据源1配置@Bean@ConfigurationProperties("spring.datasource.dynamic.datasource.master")public DataSource masterDataSource() {return DataSourceBuilder.create().build();}// 数据源2配置@Bean@ConfigurationProperties("spring.datasource.dynamic.datasource.slave")public DataSource slaveDataSource() {return DataSourceBuilder.create().build();}// 确保分页插件被所有数据源使用@Beanpublic MybatisSqlSessionFactoryBean sqlSessionFactory(DataSource dataSource, MybatisPlusInterceptor interceptor) throws Exception {MybatisSqlSessionFactoryBean sessionFactory = new MybatisSqlSessionFactoryBean();sessionFactory.setDataSource(dataSource);sessionFactory.setPlugins(interceptor); // 注入分页插件return sessionFactory;}
}

六、最佳实践:分页查询的 “黄金法则”

结合实际项目经验,总结以下最佳实践,让分页查询更高效、更可靠。

6.1 分页参数校验不可少

前端传入的pageNumpageSize可能为负数或过大,需在 Controller 层进行校验:

java

@GetMapping("/users")
public IPage<User> getUsers(@RequestParam(defaultValue = "1") Integer pageNum,@RequestParam(defaultValue = "10") Integer pageSize
) {// 校验页码(至少为1)pageNum = Math.max(pageNum, 1);// 校验页大小(1-1000之间)pageSize = Math.min(Math.max(pageSize, 1), 1000);return userService.getUserPage(pageNum, pageSize);
}

避免因参数错误导致的异常(如pageNum=0pageSize=10000)。

6.2 优先使用 BaseMapper 的分页方法

BaseMapper 提供的selectPageselectMapsPage(返回 Map 结果)等方法已足够满足大部分场景,尽量避免重复造轮子。

java

// 查询Map结果(无需定义VO)
public IPage<Map<String, Object>> getUserMapPage(Integer pageNum, Integer pageSize) {Page<Map<String, Object>> page = new Page<>(pageNum, pageSize);return userMapper.selectMapsPage(page, new QueryWrapper<User>().select("id", "name", "age"));
}

6.3 复杂场景考虑 “游标分页”

传统分页(基于pageNumpageSize)在数据量超大(如 1000 万 +)且频繁翻页时,性能会下降(因为LIMIT 1000000, 10需要扫描前 100 万行)。此时可采用 “游标分页”(基于上次查询的最后一条记录的 ID)。

示例:游标分页实现

java

public IPage<User> getUserByCursor(Integer pageSize, @RequestParam(required = false) Long lastId
) {Page<User> page = new Page<>(1, pageSize); // 页码固定为1,用lastId作为游标QueryWrapper<User> queryWrapper = new QueryWrapper<>();// 若有lastId,查询ID大于lastId的数据(假设ID自增)if (lastId != null) {queryWrapper.gt("id", lastId);}queryWrapper.orderByAsc("id"); // 按ID排序,确保游标有效return userMapper.selectPage(page, queryWrapper);
}

前端通过每次返回的最后一条记录的id作为下一次查询的lastId,实现高效的滚动加载。这种方式适合移动端列表、日志查询等场景,但不支持 “跳页”(如直接到第 10 页)。

6.4 分页查询与缓存结合

对于高频且变化不频繁的分页查询(如商品列表),可以结合缓存(如 Redis)提升性能:

java

public IPage<Product> getProductPage(Integer pageNum, Integer pageSize) {// 缓存key(包含页码和页大小)String cacheKey = "product:page:" + pageNum + ":" + pageSize;// 从Redis获取缓存IPage<Product> cachedPage = redisTemplate.opsForValue().get(cacheKey);if (cachedPage != null) {return cachedPage;}// 缓存未命中,查询数据库Page<Product> page = new Page<>(pageNum, pageSize);IPage<Product> resultPage = productMapper.selectPage(page, null);// 存入Redis(设置10分钟过期)redisTemplate.opsForValue().set(cacheKey, resultPage, 10, TimeUnit.MINUTES);return resultPage;
}

注意:缓存需根据数据更新策略(如商品修改后清除对应缓存)及时失效,避免返回脏数据。

六、总结:MyBatis-Plus 分页 —— 简单与强大的完美结合

MyBatis-Plus 的分页功能彻底改变了传统分页查询的开发模式:从 “编写两条 SQL + 手动计算分页参数” 到 “一行代码完成分页”,大幅减少了冗余代码,降低了出错概率。无论是基础的条件分页、复杂的联表查询,还是高性能的游标分页,MP 都能提供简洁高效的解决方案。

http://www.lryc.cn/news/589381.html

相关文章:

  • WebAPIs基本认知,DOM基础介绍
  • 网络基础10--ACL与包过滤
  • k8s环境使用Operator部署Seaweedfs集群(下)
  • 删除k8s卸载后残留挂载点目录
  • 设计模式二:策略模式 (Strategy Pattern)
  • 医疗数据分析中标准化的作用
  • 新方法!家长可用安卓或苹果,远程管理孩子使用iPhone的时长
  • 1MIPI 转2MIPI,支持2560*1600,75HZ.
  • RS触发器Multisim电路仿真——硬件工程师笔记
  • 分布式存储之Ceph使用指南--部署篇(未完待续)
  • CF1916D Mathematical Problem 题解
  • 【Linux】线程创建等待终止分离
  • 【2026版】Java基础面试题
  • Linux 基本操作与服务器部署
  • 第二章 OB 存储引擎高级技术
  • C/C++宏定义中do{}while(0)的妙用
  • 4-Nodejs模块化
  • 国内第一梯队终端安全产品解析:技术与场景实践
  • Video Python(Pyav)解码一
  • 如何解决 Spring Boot 使用 Maven 打包后运行失败的问题(附详细排查步骤)
  • 【GEOS-Chem模拟教程第一期上】气溶胶专用/碳气体/全化学模拟
  • [锂电池]锂电池入门指南
  • Altium Designer 25 安装与配置完整教程
  • C 语言(二)
  • 期权做空怎么操作?
  • 软文营销怎么打造口碑扩散,让品牌声量快速增长
  • 极限状态下函数开根号的计算理解(含示意图)
  • 李宏毅《生成式人工智能导论》 | 第11讲-第14讲:大型语言模型的可解释性、能力评估、安全性
  • AUTOSAR进阶图解==>AUTOSAR_SWS_FlexRayARTransportLayer
  • 【Unity】MiniGame编辑器小游戏(十四)基础支持模块(游戏窗口、游戏对象、物理系统、动画系统、射线检测)