MyBatis-Plus vs MyBatis:全面对比与选型指南
前言
MyBatis 是 Java 持久层框架中“灵活派”的代表,而 MyBatis-Plus(简称 MP)在 MyBatis 的基础上做了增强,号称“为简化而生”。很多初学者甚至资深开发者在选型时都会纠结:到底用原生 MyBatis 还是 MyBatis-Plus?
本文将从一个客观、实战的角度,全方位对比两者的区别,包括核心功能、开发效率、SQL 控制、扩展机制以及适用场景,并配以流程图和代码示例,帮助你做出合理的技术决策。
一、一句话定义
| 框架 | 定义 |
|---|---|
| MyBatis | 一款优秀的持久层框架,支持定制化 SQL、存储过程以及高级映射,避免了几乎所有的 JDBC 代码和手动设置参数。 |
| MyBatis-Plus | MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,旨在简化开发、提高效率,无侵入地提供通用 CRUD、代码生成器、条件构造器等特性。 |
官方标语:“为简化而生”
二、核心区别概览
区别速览表
| 对比维度 | MyBatis | MyBatis-Plus |
|---|---|---|
| 设计理念 | 灵活,SQL 与代码分离 | 简化开发,提高效率(约定大于配置) |
| 单表 CRUD | 需要手写 SQL 或使用注解 | 内置通用 Mapper,无需写 SQL |
| 条件构造器 | 无,需要手动拼接 SQL 或使用第三方 | 提供QueryWrapper/LambdaQueryWrapper |
| 分页实现 | 手动写方言 SQL 或用 PageHelper | 内置分页插件,自动生成 count 和分页 SQL |
| 代码生成器 | 无官方支持(可使用 MyBatis Generator) | 内置强大代码生成器,一键生成 Entity、Mapper、Service、Controller |
| 主键策略 | 手动配置或使用selectKey | 支持多种主键策略(雪花 ID、自增、UUID 等) |
| 逻辑删除 | 需要手动拦截器或自定义 SQL | 内置逻辑删除插件,@TableLogic注解 |
| 自动填充 | 无,需手动设置 createTime/updateTime | 内置自动填充功能 (MetaObjectHandler) |
| 性能损耗 | 几乎无 | 微乎其微(增强功能仅在编译/启动时生成代码) |
| 学习曲线 | 较陡,需要掌握 XML 配置、动态 SQL | 平缓,简单 CRUD 零 SQL |
| 适用场景 | 复杂查询、需要精细控制 SQL、遗留系统 | 快速开发、单表操作多、团队效率优先 |
三、核心功能详细对比
3.1 单表 CRUD 操作
MyBatis 方式:需要编写 Mapper 接口 + XML 映射文件(或注解),手写 SQL。
<!-- UserMapper.xml --><selectid="selectById"resultType="com.example.User">SELECT * FROM user WHERE id = #{id}</select>MyBatis-Plus 方式:继承BaseMapper<T>,直接调用内置方法。
publicinterfaceUserMapperextendsBaseMapper<User>{// 无需任何代码,继承即可获得 insert, deleteById, updateById, selectList 等方法}// 使用Useruser=userMapper.selectById(1L);List<User>list=userMapper.selectList(newQueryWrapper<User>().eq("age",18));3.2 条件构造器(Wrapper)
这是 MyBatis-Plus 最亮眼的功能之一,极大简化了动态查询。
// 查询 name = "张三" 且 age > 20 的用户QueryWrapper<User>wrapper=newQueryWrapper<>();wrapper.eq("name","张三").gt("age",20);List<User>users=userMapper.selectList(wrapper);// Lambda 方式(避免字段名硬编码)LambdaQueryWrapper<User>lambdaWrapper=newLambdaQueryWrapper<>();lambdaWrapper.eq(User::getName,"张三").gt(User::getAge,20);而在原生 MyBatis 中,你需要手动编写<if>标签或使用第三方条件构造器。
3.3 分页实现
MyBatis:通常结合 PageHelper 分页插件,或手动写 limit 子句。
PageHelper.startPage(1,10);List<User>list=userMapper.selectByCondition(...);PageInfo<User>pageInfo=newPageInfo<>(list);MyBatis-Plus:内置分页插件,API 更统一。
// 配置分页插件(Spring Boot 自动配置)@BeanpublicMybatisPlusInterceptormybatisPlusInterceptor(){MybatisPlusInterceptorinterceptor=newMybatisPlusInterceptor();interceptor.addInnerInterceptor(newPaginationInnerInterceptor(DbType.MYSQL));returninterceptor;}// 使用Page<User>page=newPage<>(1,10);LambdaQueryWrapper<User>wrapper=newLambdaQueryWrapper<>();wrapper.eq(User::getStatus,1);Page<User>result=userMapper.selectPage(page,wrapper);System.out.println(result.getRecords());// 数据列表System.out.println(result.getTotal());// 总记录数3.4 代码生成器
MyBatis-Plus 官方提供强大的代码生成器,可一键生成 Entity、Mapper、Service、Controller 等,大幅提升开发效率。
// 示例:AutoGenerator 配置AutoGeneratorgenerator=newAutoGenerator();// 设置数据源、包名、策略等generator.execute();MyBatis 官方没有类似的生成器,但可以使用第三方的 MyBatis Generator (MBG),不过配置相对繁琐。
3.5 逻辑删除
MyBatis:需要手动拦截 SQL 或每次写 SQL 时加上deleted=0条件。
MyBatis-Plus:在实体类字段上加@TableLogic,然后内置删除方法会自动变为更新逻辑删除字段,查询时自动过滤已删除数据。
@TableLogicprivateIntegerdeleted;3.6 自动填充(如 create_time, update_time)
MyBatis-Plus 提供了MetaObjectHandler,可以自动填充字段,无需每次手动设置。
@ComponentpublicclassMyMetaObjectHandlerimplementsMetaObjectHandler{@OverridepublicvoidinsertFill(MetaObjectmetaObject){this.strictInsertFill(metaObject,"createTime",LocalDateTime.class,LocalDateTime.now());this.strictInsertFill(metaObject,"updateTime",LocalDateTime.class,LocalDateTime.now());}@OverridepublicvoidupdateFill(MetaObjectmetaObject){this.strictUpdateFill(metaObject,"updateTime",LocalDateTime.class,LocalDateTime.now());}}四、工作流程对比
MyBatis 执行流程
MyBatis-Plus 执行流程(增强部分)
MP 并不会替换 MyBatis 的核心执行机制,而是在其基础上注入自定义 SQL 片段(通过 SQL 注入器)和拦截器链实现增强。
五、优缺点分析
MyBatis
| 优点 | 缺点 |
|---|---|
| ✅ SQL 与代码分离,精细化控制 | ❌ 单表操作仍需编写重复 SQL |
| ✅ 动态 SQL 强大(if, choose, foreach) | ❌ 无自带分页、逻辑删除等通用功能 |
| ✅ 性能可控,没有额外开销 | ❌ 代码量较大,开发效率相对低 |
| ✅ 适合复杂查询、多表联查 | ❌ 需要维护 XML 和接口映射 |
MyBatis-Plus
| 优点 | 缺点 |
|---|---|
| ✅单表 CRUD 零 SQL,开发效率高 | ❌ 多表复杂查询依然需要手写 SQL(但可混合使用) |
| ✅ 内置分页、条件构造器、逻辑删除、自动填充 | ❌ 过度依赖 MP 特有 API 可能降低代码可移植性 |
| ✅ 代码生成器一键生成基础代码 | ❌ 如果滥用 Wrapper 会导致业务逻辑泄露到数据层 |
| ✅ 无侵入,可与 MyBatis 共存 | ❌ 对动态表名、多租户等高级场景需要额外配置 |
| ✅ 社区活跃,文档丰富 |
六、使用场景与选型建议
| 场景 | 推荐框架 | 理由 |
|---|---|---|
| 快速开发中小型项目(单体/微服务) | MyBatis-Plus | 单表操作多,通用功能节省大量时间 |
| 大型复杂企业系统,有资深 SQL 优化人员 | MyBatis 或MyBatis + MP 混合 | 复杂查询需要精细控制 SQL,MP 仅用于单表 |
| 遗留系统改造,不想大量重写 | 只引入 MP 作为辅助 | MP 无侵入,可以逐步替换单表操作 |
| 团队对 MyBatis 非常熟悉且已有成熟封装 | MyBatis | 避免引入额外依赖,保持技术栈统一 |
| 需要极高性能,对 SQL 执行计划有极致要求 | MyBatis | MP 的自动生成 SQL 可能不是最优(可手动改写) |
| 学习/教学/个人项目 | MyBatis-Plus | 快速出活,体验现代开发方式 |
最佳实践:MyBatis-Plus + MyBatis 混合使用。单表操作用 MP,复杂多表查询、存储过程依然写在 XML 中,互不干扰。
七、代码示例:同一个功能两种实现
假设实现:根据用户名和年龄区间查询用户列表,并分页展示
MyBatis 实现
<!-- UserMapper.xml --><selectid="selectByCondition"resultType="User">SELECT * FROM user<where><iftest="name != null and name != ''">AND name = #{name}</if><iftest="minAge != null">AND age >= #{minAge}</if><iftest="maxAge != null">AND age <= #{maxAge}</if></where></select>// 配合 PageHelperPageHelper.startPage(pageNum,pageSize);List<User>list=userMapper.selectByCondition(name,minAge,maxAge);PageInfo<User>pageInfo=newPageInfo<>(list);MyBatis-Plus 实现
// 无需修改 XML,直接使用 MPPage<User>page=newPage<>(pageNum,pageSize);LambdaQueryWrapper<User>wrapper=newLambdaQueryWrapper<>();wrapper.eq(StringUtils.hasText(name),User::getName,name).ge(minAge!=null,User::getAge,minAge).le(maxAge!=null,User::getAge,maxAge);Page<User>result=userMapper.selectPage(page,wrapper);可见 MP 代码量显著减少,且可读性更高。
八、总结
- MyBatis-Plus 不是 MyBatis 的替代品,而是增强工具。它完全兼容 MyBatis,你可以随时切换回原生方式。
- 对于单表操作,MP 可以极大提升效率;对于复杂查询,依然推荐手写 SQL 以获得最佳性能和控制力。
- 选型时建议:中小型项目直接用 MyBatis-Plus;大型项目以 MyBatis 为主,引入 MP 仅用于通用 CRUD。
- 从长远维护角度看,MP 遵循“约定大于配置”,降低了团队沟通成本,是当前 Java 后端开发的主流选择之一。
官方文档:MyBatis-Plus 官网