MyBatis-Plus与PageHelper分页方案对比
一、MyBatis-Plus简介
MyBatis-Plus(简称MP)是一个MyBatis的增强工具,它在MyBatis的基础上只做增强不做改变,为简化开发、提高效率而生。其核心理念是"无侵入"------引入它不会对现有工程产生影响,而是像丝般顺滑地增强原生MyBatis能力。
1.1 MyBatis-Plus的核心特性
- 强大的CRUD操作:内置通用Mapper和通用Service,仅需少量配置即可实现单表大部分CRUD操作,搭配条件构造器满足各类使用需求
- Lambda表达式支持:通过Lambda表达式编写查询条件,避免字段名拼写错误
- 内置分页插件:基于MyBatis物理分页,配置后分页查询等同于普通List查询
- 内置代码生成器:快速生成Mapper、Model、Service、Controller层代码
- 丰富的插件生态:支持乐观锁、逻辑删除、多租户、性能分析等实用功能
1.2 MyBatis-Plus的基本使用示例
java
// Mapper接口(无需编写任何SQL)
public interface UserMapper extends BaseMapper<User> { }
// Service层------单表CRUD零SQL
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
// 无需手写SQL的插入
public void addUser(User user) {
userMapper.insert(user);
}
// 条件查询:查询年龄大于18的用户
public List<User> getAdultUsers() {
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.gt(User::getAge, 18);
return userMapper.selectList(wrapper);
}
// 分页查询
public IPage<User> queryByPage(int pageNum, int pageSize) {
Page<User> page = new Page<>(pageNum, pageSize);
return userMapper.selectPage(page, null);
}
}
二、分页实现对比
2.1 PageHelper vs MyBatis-Plus:分页机制对比
PageHelper是一个独立于ORM框架的通用MyBatis插件,而MyBatis-Plus的分页插件PaginationInnerInterceptor是其生态中的原生组件,深度绑定MP的实体模型和链式API。
以下是两者的核心差异对比表:
| 对比维度 | PageHelper | MyBatis-Plus |
|---|---|---|
| 定位 | 独立的MyBatis分页插件 | MyBatis增强框架中的内置组件 |
| 分页插件 | PageHelper | PaginationInnerInterceptor |
| 分页参数传递 | 通过PageHelper.startPage()存入ThreadLocal |
通过Page<T>对象作为方法入参 |
| 分页对象类型 | PageInfo<T> |
IPage<T> / Page<T> |
| API调用方式 | 分页开启与查询代码分离 | 分页参数与方法调用一体化 |
| 与框架耦合度 | 低,可独立使用 | 高,需配合MP生态使用 |
| 适用场景 | 任何Mybatis项目,轻量级分页需求 | MP项目中的一体化分页,配合条件构造器使用 |
2.2 代码实现对比
PageHelper实现方式(开启与查询分离):
java
// 1. 开启分页
PageHelper.startPage(pageNum, pageSize);
// 2. 执行查询(无需传递分页参数)
List<User> users = userMapper.selectList();
// 3. 封装结果
PageInfo<User> pageInfo = new PageInfo<>(users);
MyBatis-Plus实现方式(一体化调用):
java
// 创建分页对象并直接传入Mapper方法
Page<User> page = new Page<>(pageNum, pageSize);
IPage<User> userPage = userMapper.selectPage(page, null);
// 分页信息已封装在userPage中
long total = userPage.getTotal();
List<User> records = userPage.getRecords();
2.3 核心差异分析
1. 分页参数传递方式的差异
- PageHelper :分页参数存储在
ThreadLocal中,与Mapper查询分离。这种设计虽然简洁,但要求startPage()调用后必须紧跟着执行Mapper方法,否则可能出现分页失效或线程安全问题。 - MyBatis-Plus :分页参数通过
Page<T>对象作为方法参数显式传递,与查询代码融为一体,无需担心ThreadLocal污染问题,代码意图更加清晰。
2. SQL改写方式的差异
两者均通过拦截器拦截SQL执行,但拦截点略有不同。MyBatis-Plus的PaginationInnerInterceptor工作原理主要是通过拦截MyBatis的SQL语句,根据分页参数自动添加LIMIT和OFFSET子句,在Executor执行查询方法前判断是否需要分页,如果需要则修改SQL语句,添加分页条件。
3. 与条件构造器的配合差异
MyBatis-Plus的分页与条件构造器天然集成,使得复杂条件的分页查询编写极为流畅:
java
// MyBatis-Plus:条件构造器与分页无缝配合
Page<User> page = new Page<>(1, 10);
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>()
.eq(User::getStatus, 1)
.between(User::getAge, 18, 30)
.orderByDesc(User::getCreateTime);
IPage<User> result = userMapper.selectPage(page, wrapper);
而PageHelper需要配合MyBatis原生的动态SQL(如<if>、<where>标签)来构建查询条件,代码量相对较多。
三、功能特性全方位对比
除了分页机制外,两个方案在整体功能层面存在显著差异。PageHelper专注于解决分页问题,而MyBatis-Plus是一个完整的MyBatis增强框架。以下是从几个核心维度进行的对比:
| 对比维度 | PageHelper | MyBatis-Plus |
|---|---|---|
| SQL编写量 | 仍需手动编写大部分CRUD SQL | 单表CRUD零SQL,通过BaseMapper直接调用 |
| 条件构造器 | 无,需使用原生动态SQL | 提供QueryWrapper/LambdaQueryWrapper,链式调用 |
| 逻辑删除 | 需手动实现 | @TableLogic注解一键配置 |
| 乐观锁 | 需手动实现版本控制 | @Version注解+内置插件 |
| 代码生成器 | 需依赖MyBatis Generator | 内置FastAutoGenerator,快速生成全套代码 |
| 自动填充 | 需手动实现 | @TableField(fill=...) + MetaObjectHandler |
| 学习成本 | 低,只需了解分页API | 中,需熟悉MP的API和条件构造器 |
| 项目定位 | 分页功能插件 | 完整的MyBatis增强框架 |
从上表可以看出,PageHelper是一个轻量级的分页解决方案 ,仅解决分页这一个问题;而MyBatis-Plus是一个全面的MyBatis增强工具,分页只是其众多开箱即用功能中的一项。
3.1 功能差异详解
SQL编写量差异
MyBatis-Plus通过继承BaseMapper<T>,开发者无需编写任何SQL即可完成单表CRUD操作[reference:16]。以简单的根据ID查询为例,MyBatis-Plus仅需一行userMapper.selectById(1L),而PageHelper配合原生MyBatis则需要手写XML SQL或在接口上编写注解SQL。
条件构造器差异
MyBatis-Plus的Lambda条件构造器是其一大亮点:
java
// Lambda条件构造器:类型安全,避免字段名拼写错误
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getStatus, 1) // status = 1
.like(User::getName, "张") // name like '%张%'
.orderByDesc(User::getCreateTime); // order by create_time desc
List<User> users = userMapper.selectList(wrapper);
原生MyBatis搭配PageHelper时,需要手写XML动态SQL,字段名以字符串形式传递,容易出错且不易维护。
四、使用场景与选型建议
4.1 选型决策流程图
以下流程图可帮助读者快速判断在具体项目中应该选择哪种方案:
是
否
是
否
是
否,需要更多功能
开始:选择分页方案
项目是否已使用MyBatis-Plus?
直接使用MP内置分页
推荐:PaginationInnerInterceptor
项目是否以单表CRUD为主?
推荐引入MyBatis-Plus
获得分页+CRUD增强双重收益
是否只需要分页功能?
PageHelper
轻量级,零侵入
考虑MyBatis-Plus
功能更全面
结束
4.2 场景化建议
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 纯MyBatis项目,仅需分页功能 | PageHelper | 轻量级,零侵入,配置简单[reference:17] |
| 新项目,以单表CRUD为主 | MyBatis-Plus | 开发效率高,单表零SQL,分页开箱即用[reference:18] |
| 项目存在大量复杂多表关联SQL | PageHelper或MyBatis+MP混合 | MyBatis-Plus的复杂查询仍需手写SQL,两者可混用 |
| 追求极致开发效率 | MyBatis-Plus | 代码生成器+条件构造器+通用CRUD大幅减少代码量 |
| 已有MyBatis-Plus项目 | MP内置分页 | 生态统一,API风格一致,无需引入额外依赖 |
4.3 混合使用策略
在实际项目中,完全可以采用MyBatis + MyBatis-Plus混合使用的策略:单表CRUD使用MyBatis-Plus简化开发,复杂查询(多表关联、子查询等)使用MyBatis手写SQL保证灵活性和性能。
五、性能对比
根据最新的压力测试结果(Spring Boot 4 + JDK25虚拟线程环境),在常规分页场景(COUNT + 列表)下,PageHelper和MyBatis-Plus的吞吐和延迟表现基本持平,性能差异极小,瓶颈主要在数据库侧。
| 测试场景 | PageHelper | MyBatis-Plus |
|---|---|---|
| 常规分页(中低并发) | 86~90 ops/s | 与PageHelper持平 |
| 常规分页(中高并发) | 性能稳定 | 与PageHelper持平 |
| 极限高并发(2000~3000) | 第一梯队 | 同处第一梯队 |
核心结论:在常规分页场景下,两者性能无实质性差异,完全能满足日常业务需求。
六、总结与建议
| 对比维度 | PageHelper | MyBatis-Plus |
|---|---|---|
| 核心定位 | 分页插件 | MyBatis增强框架 |
| 学习成本 | 低 | 中 |
| 开发效率 | 中(仍需写CRUD SQL) | 高(单表CRUD零SQL) |
| 功能丰富度 | 单一分页功能 | 分页+CRUD+代码生成+条件构造器+... |
| 与MyBatis兼容性 | 完全兼容 | 完全兼容,可混用 |
| 推荐场景 | 纯MyBatis项目,只需分页 | 新项目,追求开发效率,单表操作为主 |
选择建议:
- 若项目已是MyBatis-Plus:直接使用内置分页,生态统一,无需额外引入PageHelper
- 若项目是原生MyBatis:仅需要分页则选PageHelper,轻量简单;若还有其他CRUD简化需求,建议整体升级至MyBatis-Plus
- 复杂多表查询场景:无论选择哪种分页方案,复杂SQL仍需手写,两者可混合使用
参考资源
- MyBatis-Plus官方文档:https://baomidou.com/
- PageHelper官方文档:https://pagehelper.github.io/