1. 项目概述
在Java企业级开发中,分页查询是最基础却最频繁使用的功能之一。传统的分页实现往往需要编写大量重复代码,而SpringBoot与Mybatis-plus的组合为我们提供了一套优雅的解决方案。本文将带你从零开始,用最简洁的方式实现标准化的分页功能。
我曾在多个电商和ERP系统中实现过复杂的分页需求,发现很多开发者虽然会用Mybatis-plus的分页插件,但对底层机制和定制化技巧了解不深。下面分享的不仅是基础用法,还包括实际项目中积累的优化经验。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 环境准备与基础配置
2.1 依赖引入
首先确保pom.xml中包含必要依赖(SpringBoot 2.7.x示例):
xml复制<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.3</version>
</dependency>
注意:Mybatis-plus版本需要与SpringBoot版本匹配,3.5.x系列支持SpringBoot 2.5-2.7,如需使用SpringBoot 3.0+需选择mybatis-plus 3.5.7+
2.2 分页插件配置
在SpringBoot启动类或配置类中添加:
java复制@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
这里有几个关键点:
DbType.MYSQL需根据实际数据库类型调整- 如需支持多数据源分页,需要为每个数据源单独配置
- 高并发场景建议设置
overflow属性防止恶意超大分页
3. 基础分页实现
3.1 Controller层设计
标准的RESTful风格分页接口示例:
java复制@GetMapping("/users")
public R<Page<User>> listUsers(
@RequestParam(defaultValue = "1") int current,
@RequestParam(defaultValue = "10") int size) {
Page<User> page = new Page<>(current, size);
return R.ok(userService.page(page));
}
3.2 Service层实现
Mybatis-plus的IService接口已提供基础分页方法:
java复制public interface UserService extends IService<User> {
// 默认实现已包含page方法
}
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
implements UserService {
// 无需额外实现
}
3.3 自定义分页查询
当需要复杂查询条件时:
java复制public Page<User> queryByCondition(PageQuery query) {
return lambdaQuery()
.eq(StringUtils.isNotBlank(query.getName()), User::getName, query.getName())
.between(query.getStartTime() != null, User::getCreateTime,
query.getStartTime(), query.getEndTime())
.page(new Page<>(query.getCurrent(), query.getSize()));
}
4. 高级技巧与优化
4.1 性能优化方案
- 禁止COUNT查询(当不需要总记录数时):
java复制Page<User> page = new Page<>(current, size);
page.setSearchCount(false);
- 自定义COUNT SQL:
java复制@Select("SELECT * FROM user ${ew.customSqlSegment}")
Page<User> customPage(Page<User> page, @Param(Constants.WRAPPER) Wrapper<User> wrapper);
4.2 特殊分页需求处理
跨库分页(如ShardingSphere场景):
java复制PaginationInnerInterceptor interceptor = new PaginationInnerInterceptor();
interceptor.setOverflow(true); // 允许溢出分页
interceptor.setMaxLimit(500L); // 单页最大500条
内存分页(先查全部再分页):
java复制List<User> allData = userMapper.selectList(null);
List<User> pageData = allData.stream()
.skip((current - 1) * size)
.limit(size)
.collect(Collectors.toList());
5. 常见问题排查
5.1 分页失效的典型原因
- 未配置分页插件
- Page对象创建位置不对(必须在Service方法参数中创建)
- 自定义SQL未使用
${ew.customSqlSegment} - 多数据源环境下未为每个数据源配置插件
5.2 性能问题分析
当分页查询变慢时,检查:
- 是否查询了不必要的字段(避免SELECT *)
- 大表分页是否使用覆盖索引
- 深度分页是否考虑游标分页替代方案
6. 最佳实践建议
- 统一分页参数封装:
java复制@Data
public class PageQuery {
private Integer current = 1;
private Integer size = 10;
// 其他查询参数...
}
- 前端分页适配:
javascript复制// 响应数据结构示例
{
"code": 200,
"data": {
"records": [...],
"total": 100,
"size": 10,
"current": 1
}
}
- Swagger文档集成:
java复制@Operation(summary = "分页查询用户")
@Parameters({
@Parameter(name = "current", description = "当前页", example = "1"),
@Parameter(name = "size", description = "每页条数", example = "10")
})
实际项目中,我推荐将分页逻辑封装到基类Controller中,通过AOP统一处理分页参数转换和响应格式包装。对于百万级数据表,建议采用基于create_time的游标分页方案替代传统LIMIT分页
