作为Java后端开发中最常用的ORM框架之一,MyBatis-Plus的分页功能设计得非常巧妙。今天我将结合自己多年使用经验,带大家彻底掌握这个分页插件的核心原理和最佳实践。
在传统MyBatis中实现分页通常有几种方式:
但这些方式都存在明显缺陷:要么侵入业务代码,要么需要针对不同数据库做适配。MyBatis-Plus的分页插件通过拦截器机制,统一了分页API,同时自动识别数据库类型生成对应的分页SQL。
关键优势:一套代码兼容多种数据库,分页逻辑与业务代码解耦
先来看配置类的完整实现方案:
java复制@Configuration
public class MybatisConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 分页插件配置
PaginationInnerInterceptor paginationInterceptor = new PaginationInnerInterceptor();
paginationInterceptor.setDbType(DbType.MYSQL); // 数据库类型
paginationInterceptor.setMaxLimit(1000L); // 单页最大记录数
paginationInterceptor.setOverflow(true); // 超出页码是否返回第一页
// 防止全表更新与删除插件
BlockAttackInnerInterceptor blockAttackInterceptor = new BlockAttackInnerInterceptor();
interceptor.addInnerInterceptor(paginationInterceptor);
interceptor.addInnerInterceptor(blockAttackInterceptor);
return interceptor;
}
}
DbType:支持MySQL、ORACLE、POSTGRE_SQL等主流数据库MaxLimit:防止恶意传参导致的大数据量查询Overflow:当页码超出范围时的处理策略在SAAS系统中,我们通常需要增加租户过滤器:
java复制// 添加租户拦截器
TenantLineInnerInterceptor tenantInterceptor = new TenantLineInnerInterceptor();
tenantInterceptor.setTenantLineHandler(new TenantLineHandler() {
@Override
public String getTenantIdColumn() {
return "tenant_id";
}
@Override
public Expression getTenantId() {
return new StringValue("当前租户ID");
}
});
interceptor.addInnerInterceptor(tenantInterceptor);
java复制// 构建分页条件
Page<User> page = Page.of(1, 10); // 当前页,每页大小
// 执行分页查询
Page<User> result = userMapper.selectPage(page, null);
// 获取分页信息
long total = result.getTotal();
List<User> records = result.getRecords();
java复制LambdaQueryWrapper<User> wrapper = Wrappers.lambdaQuery();
wrapper.gt(User::getAge, 18)
.like(User::getName, "张");
Page<User> page = Page.of(1, 10);
userMapper.selectPage(page, wrapper);
Mapper接口定义:
java复制@Select("SELECT * FROM user WHERE age > #{param1}")
IPage<User> selectByAge(IPage<?> page, Integer age);
调用方式:
java复制Page<User> page = Page.of(1, 10);
userMapper.selectByAge(page, 18);
对于复杂关联查询,建议使用VO对象接收:
java复制@Select("SELECT u.*, d.name AS deptName FROM user u LEFT JOIN department d ON u.dept_id=d.id")
IPage<UserVO> selectUserWithDept(IPage<?> page);
java复制try (Cursor<User> cursor = userMapper.selectCursor(page, wrapper)) {
cursor.forEach(user -> {
// 处理每条数据
});
}
java复制Page<User> page = Page.of(1, 10).setSearchCount(true);
userMapper.selectPage(page, wrapper);
COUNT查询慢:
@SqlParser(filter=true)跳过特定查询的count大偏移量问题:
java复制// 使用where条件替代大offset
wrapper.gt(User::getId, lastMaxId)
.last("LIMIT " + pageSize);
MyBatis-Plus分页是通过拦截StatementHandler实现的:
| 异常现象 | 可能原因 | 解决方案 |
|---|---|---|
| 分页失效 | 未正确配置拦截器 | 检查配置类是否被Spring加载 |
| 总数不准 | 有自定义的WHERE条件 | 使用@SqlParser注解 |
| 性能低下 | 缺少合适索引 | 为查询条件添加复合索引 |
继承PaginationInnerInterceptor实现:
java复制public class CustomPaginationInterceptor extends PaginationInnerInterceptor {
@Override
public void beforeQuery(Executor executor, MappedStatement ms,
Object parameter, RowBounds rowBounds, ResultHandler resultHandler,
BoundSql boundSql) {
// 自定义分页逻辑
}
}
java复制@Bean
@ConditionalOnBean(DataSource.class)
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 动态获取数据源类型
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(dbType -> {
return DynamicDataSourceContextHolder.getDbType();
}));
return interceptor;
}
java复制public Page<User> findByDept(String deptId, org.springframework.data.domain.Pageable pageable) {
Page<User> mpPage = Page.of(pageable.getPageNumber(), pageable.getPageSize());
LambdaQueryWrapper<User> wrapper = Wrappers.lambdaQuery(User.class)
.eq(User::getDeptId, deptId);
return userMapper.selectPage(mpPage, wrapper);
}
配置建议:
性能优化:
事务处理:
java复制@Transactional
public Page<User> getUsersWithLock(int pageNo) {
Page<User> page = Page.of(pageNo, 10);
userMapper.selectPage(page, Wrappers.<User>query()
.forUpdate());
return page;
}
监控方案:
java复制paginationInterceptor.setProperties(properties -> {
metrics.recordQuery(properties.getSql());
});
在实际项目中,我发现合理使用分页插件可以提升30%以上的列表查询性能。特别是在处理千万级数据时,正确的分页策略配合索引优化,响应时间可以从秒级降到毫秒级