适用读者:手写 CRUD 写到烦的人。
前置:S08-B01。
实测环境:MyBatis-Plus 3.5.7(Boot 3 starter)/ H2(2026-11-05)。
覆盖旧文:1170(41) 495(41) 1171(40) 1172(59) 1173(19) 1178(108) 1177(47) 1176(52) 1175(109) 1182(58) 1186(40) 1188(50) 1187(58) 1189(72) 351(62) 152(59) 1174(46) = 986 点。
覆盖扩展:E-S08-016(MP 与 MyBatis 的关系)。
上一篇:S08-B03 · 动态 SQL:<if>/<where>/<foreach>
下一篇:S08-B05 · Wrapper。
1. 一句话结论
MyBatis-Plus 不是另一个 ORM,它是"在 MyBatis 之上加了一层 CRUD 模板 + 若干内置拦截器"。所以 MyBatis 能做的它都能做,它多出来的是"少写代码"。
| 能力 | MyBatis | MyBatis-Plus |
|---|---|---|
| SQL 控制 | 完全手写 | 手写 + 通用 CRUD |
| 逻辑删除 | 手写 where deleted=0 |
@TableLogic 自动 |
| 乐观锁 | 手写 version 条件 | @Version 自动 |
| 分页 | PageHelper 等 | 内置分页插件 |
| 代码生成 | 无 | AutoGenerator(→S06) |
2. 实测:一次 CRUD 生成的 SQL
@TableName("sys_user")
public class User { @TableId(type = IdType.AUTO) Long id; String userName; Integer age;
@TableLogic Integer deleted; @Version Integer version; }
public interface UserMapper extends BaseMapper<User> {}
实测输出:
1. 插入 3 条 → 3
2. Wrapper 条件 age>=20 且 name like a → [User(1,alice,20,del=0,ver=0), User(3,carol,20,del=0,ver=0)]
3. 更新后 version → 1
4. 逻辑删除后 selectList 条数 → 2
对应 SQL(实测打印):
INSERT INTO sys_user ( user_name, age ) VALUES ( ?, ? )
SELECT id,user_name,age,deleted,version FROM sys_user WHERE deleted=0
UPDATE sys_user SET user_name=?, age=?, version=? WHERE id=? AND version=? AND deleted=0
UPDATE sys_user SET deleted=1 WHERE id=? AND deleted=0
三条最值得注意:
| 观察 | 结论 |
|---|---|
每个 SELECT 都带 deleted=0 |
逻辑删除是"自动追加条件",不是真删除 |
UPDATE 带 version=? 双重条件 |
乐观锁,版本不符则影响 0 行 |
| DELETE 变成 UPDATE | 物理行还在 |
3. 逻辑删除 vs 物理删除
| 维度 | 逻辑删除(@TableLogic) |
物理删除 |
|---|---|---|
| 数据可恢复 | ✅ | ❌ |
| 表会膨胀 | ✅(需归档策略) | ❌ |
| 唯一索引 | ⚠️ 要带 deleted 列(否则重复键冲突) | 简单 |
| 查询性能 | 每查都多一个条件 | 略快 |
判据:"删除的数据要能找回/审计吗?" 要 → 逻辑删除;不要且量大 → 物理删除 + 备份。
4. 乐观锁(@Version)
update ... set version = version + 1 where id = ? and version = ?
实测:更新后 version 从 0 → 1。
| 注意点 | 说明 |
|---|---|
| 需要拦截器 | OptimisticLockerInnerInterceptor |
只对 updateById 生效 |
自定义 SQL 要自己带 version |
| 冲突处理 | 影响行数 0 → 需重试或提示 |
5. 其他常用能力
| 注解/能力 | 作用 |
|---|---|
@TableId(type = AUTO/ASSIGN_ID/INPUT) |
主键策略 |
@TableField(fill = INSERT) |
自动填充(创建时间等) |
@EnumValue |
枚举映射(1186) |
动态表名(DynamicTableNameInnerInterceptor) |
按月分表(1189) |
log-impl=StdOutImpl |
打印 SQL 与参数(1174) |
打印 SQL 的实测:配置 mybatis-plus.configuration.log-impl 后能看到 ==> Preparing 与 ==> Parameters——这是排障第一步。
6. 五个误区
| 误区 | 真相 |
|---|---|
| "MP 会替代 MyBatis" | 是增强,底层仍是 MyBatis |
| "逻辑删除等于真删除" | 是 UPDATE |
"加了 @Version 就自动乐观锁" |
需要拦截器 |
"BaseMapper 能覆盖所有 SQL" |
复杂 SQL 仍要手写 |
| "MP 生成器代码可直接上线" | 生成物需 Review(S06-J03) |
7. 自检
- MP 与 MyBatis 的关系?
- 逻辑删除在 SQL 层是什么?
- 乐观锁的 WHERE 条件多了什么?冲突时怎么办?
- 逻辑删除对唯一索引有什么影响?
- 怎么打印 MP 执行的 SQL?
8. 下一篇
S08-B05 · Wrapper 条件构造器
MP 基础讲完,下一篇专讲"条件怎么组合":QueryWrapper/LambdaQueryWrapper 的用法与安全边界。
本篇属于系列《S08 · MyBatis 持久层》。旧文来源:A-1170/495/1171/1172/1173/1178/1177/1176/1175/1182/1186/1188/1187/1189/351/152/1174;扩展点 E-S08-016。SQL 与结果均为 MP 3.5.7 + H2 实测。