写在前面
如果你写过原生 MyBatis,一定经历过这种崩溃时刻——
| |
每张表都要写一遍 CRUD,字段一变就改十几个 XML,任何"按字段查"“按字段更新"都要新加一段 SQL。这种重复劳动催生了两个现象级开源项目:
- 通用 Mapper(tk.mybatis):刘增辉(abel533)2014 年开源
- MyBatis-Plus(MP):苞米豆团队 2016 年开源
两者都自称"MyBatis 增强工具”,新人选型时常常分不清。本文把这两个项目从设计哲学、能力边界、典型用法、踩坑点一一对照,给出明确的选型建议。
一句话定位
通用 Mapper:在 MyBatis 上提供"通用 SQL 模板",让你不写 XML 就能完成基础 CRUD。
MyBatis-Plus:在 MyBatis 上提供"增强能力 + 工具集",目标是把 MyBatis 用得像 JPA 一样省事,同时保留 MyBatis 的灵活性。
通用 Mapper 是"做减法"——少写 XML;MyBatis-Plus 是"做加法"——给你一整套生态工具。
一、通用 Mapper 是怎么工作的
通用 Mapper 的核心思想:给你一个泛型 Mapper<T>,里面已经定义了一堆通用方法,框架在运行时根据泛型类型生成对应的 SQL。
| |
实体类用 JPA 风格注解:
| |
它的核心方法分几大类:
| 接口 | 提供能力 |
|---|---|
BaseSelectMapper | selectByPrimaryKey 等 |
BaseInsertMapper | insert |
BaseUpdateMapper | updateByPrimaryKey |
BaseDeleteMapper | deleteByPrimaryKey |
ConditionMapper | 通过 Example 条件查询 |
RowBoundsMapper | 分页(基于 RowBounds) |
复杂条件用 Example:
| |
风格非常贴近 JPA Specification,有点"老 SSH 时代"的味道。
二、MyBatis-Plus 是怎么工作的
MyBatis-Plus 的核心入口是 BaseMapper<T> 和 IService<T> 两层抽象:
| |
实体类用 MP 自家注解(也可继承 Model<T>):
| |
最闪光的是它的 LambdaQueryWrapper——以方法引用代替字符串字段名,全程类型安全:
| |
字段重命名时编译器会立刻报错,再也不用拼字符串。
三、能力对比
| 通用 Mapper | MyBatis-Plus | |
|---|---|---|
| 基础 CRUD | ✓ | ✓ |
| 条件构造 | Example(字符串字段) | LambdaQueryWrapper(类型安全) |
| 分页 | RowBounds(物理分页要插件) | PaginationInterceptor(开箱即用) |
| 主键策略 | JPA @GeneratedValue | IdType.AUTO/INPUT/ASSIGN_ID/ASSIGN_UUID 等 |
| 自动填充 | ✗ | @TableField(fill = ...) + Handler |
| 逻辑删除 | ✗ | @TableLogic 自动 where deleted=0 |
| 乐观锁 | ✗ | @Version 自动 where version=#{old} |
| 多租户 | ✗ | TenantLineInnerInterceptor |
| 数据权限/字段加密 | ✗ | 插件机制 |
| 代码生成器 | 第三方 | 官方代码生成器 + AI Friendly |
| ActiveRecord | ✗ | Model<T>(争议特性,不推荐用) |
| 性能分析插件 | ✗ | 慢 SQL 输出、SQL 注入检测 |
简单说:通用 Mapper 只解决"少写 CRUD SQL",MyBatis-Plus 是一整套生态。
四、设计哲学差异
通用 Mapper:贴近 JPA、最小侵入
- 用的是
javax.persistence.*注解,和 JPA 互通——理论上可以无缝切到 Spring Data JPA - 不引入新概念,学习成本极低
- 一切以"少写 SQL"为目标,不试图替你做更多事
MyBatis-Plus:全套生态、约定大于配置
- 自家注解
@TableId、@TableField、@TableLogic等,绑定较深 - 提供了大量"开箱即用的便利"——逻辑删除、乐观锁、自动填充、分页都不用自己写代码
- 从 ORM 一直管到代码生成、慢 SQL 监控
简单总结:
通用 Mapper 是"工具",MyBatis-Plus 是"框架"。
五、典型场景对比
场景 1:分页
通用 Mapper 自己只支持 RowBounds,物理分页要配合 PageHelper:
| |
MyBatis-Plus 自带分页插件:
| |
场景 2:逻辑删除
通用 Mapper 没有原生支持,要么写 SQL,要么自己注入 SqlInterceptor。
MyBatis-Plus 一个注解搞定:
| |
之后所有 selectXxx、deleteXxx 自动带上 WHERE deleted = 0,省心。
场景 3:自动填充 createTime / updateTime
通用 Mapper 要靠拦截器或自己手写。
MyBatis-Plus:
| |
这种"业务无感"的能力是 MP 的杀手锏。
六、坑与争议
通用 Mapper 的坑
- Example 用字符串字段名——重构改字段名时不报错,运行时才挂
- 生态薄——分页、性能监控、代码生成都要自己整合第三方
- 维护活跃度低——近几年更新明显放缓
MyBatis-Plus 的争议点
Wrapper写多了 SQL 会变难读——复杂查询还是建议回到 XML- ActiveRecord (
Model<T>) 模式虽然炫,但耦合实体和持久层,不推荐生产用 - 绑定较深——后期想换框架成本比通用 Mapper 高
- 历史上有过 SQL 注入 CVE——升级版本要及时跟
最值得提醒的一点:Wrapper 不要写超过 3 层的复杂条件。可读性是 SQL 维护的天花板。
七、选型建议
| 通用 Mapper | MyBatis-Plus | |
|---|---|---|
| 单表 CRUD 占主导 | ✓ | ✓✓ (更顺手) |
| 复杂多表 SQL | XML 写 | XML + Wrapper 混用 |
| 团队熟悉 JPA | ✓ | △ |
| 想用全套生态工具 | ✗ | ✓ |
| 介意框架侵入 | ✓ 轻 | △ 较重 |
| 项目活跃度需求 | △ | ✓ |
| 学习曲线 | 平 | 略陡(Wrapper、插件多) |
| 社区资料 | 较少 | 大量 |
给一个直接的建议:
新项目优先 MyBatis-Plus,社区活跃、生态完整、Lambda 类型安全,几乎没有理由选通用 Mapper。
老项目已用通用 Mapper 且稳定,没必要为了 MP 重构;对接新表时可以两者共存,MP 接
BaseMapper<T>,通用 Mapper 留旧Mapper<T>,互不干扰。
八、共存方案
如果项目里同时存在两套 Mapper 也不冲突,但有几个细节:
- 包路径要分开——别让一个 Mapper 同时继承两套接口
- MapperScan 要明确:
| |
- 实体类注解最好不要混用——同一个实体要么用
javax.persistence.*,要么用@TableName/@TableId,别两边都加
小结
把全文压成一句话:
通用 Mapper 是 MyBatis 的"轻量减法",MyBatis-Plus 是 MyBatis 的"完整加法"。在 2019 年之后的新项目,几乎没有理由再选通用 Mapper。
不管选哪个,记住几个一致的工程纪律:
- 不要被工具骗了,别碰复杂多表 Wrapper——XML 永远是复杂查询的归宿
- 不要写 ActiveRecord——实体类只描述结构,不调持久层方法
- 不要忘了原生 MyBatis 的能力——MP/通用 Mapper 都只是 MyBatis 的扩展,复杂场景永远可以回到
Mapper.xml
把工具当工具用,别让工具反过来定义你的代码风格——这是任何框架选型都通用的原则。