MyBatis-Plus 增强
Java 中级 · 持久层框架
MyBatis-Plus(简称 MP)是 MyBatis 的增强工具:只做增强不做改变——引入后原有的 MyBatis 用法完全不受影响,同时白送你通用 CRUD、条件构造器、分页、代码生成、逻辑删除。理解它的实现原理(尤其是「SQL 是怎么被拼出来的」),才能用得放心。
一、通用 CRUD:BaseMapper
继承 BaseMapper<T> 即获得 17 个通用方法,无需写 XML。
@Mapper
public interface UserMapper extends BaseMapper<User> { }
// 使用
User u = userMapper.selectById(1L);
List<User> list = userMapper.selectList(new QueryWrapper<User>().eq("status", 1));
userMapper.insert(new User("Tom", 18));
userMapper.deleteById(1L);| 方法 | 说明 |
|---|---|
insert(entity) | 插入(自动回填自增 ID) |
deleteById(id) | 按主键删 |
updateById(entity) | 按主键更新非 null 字段 |
selectById(id) | 按主键查 |
selectList(wrapper) | 条件列表 |
selectCount(wrapper) | 计数 |
selectPage(page, wrapper) | 分页(需分页插件) |
主键策略
@TableId(type = IdType.AUTO) 数据库自增;IdType.ASSIGN_ID 用雪花算法生成 Long;IdType.ASSIGN_UUID 生成字符串。分布式下推荐 ASSIGN_ID。
二、条件构造器:SQL 是怎么被拼出来的
QueryWrapper / LambdaQueryWrapper 是 MP 的灵魂。它把链式调用收集成一个条件树,再在 SQL 注入时拼成 SELECT ... WHERE ...。
// 普通 Wrapper:列名硬编码字符串,易错
QueryWrapper<User> qw = new QueryWrapper<>();
qw.eq("status", 1).gt("age", 18).orderByDesc("id");
// Lambda Wrapper:方法引用取列名,编译期安全(推荐)
LambdaQueryWrapper<User> lqw = new LambdaQueryWrapper<>();
lqw.eq(User::getStatus, 1)
.gt(User::getAge, 18)
.like(StringUtils.hasText(name), User::getName, name) // 条件成立才拼
.orderByDesc(User::getId);
List<User> list = userMapper.selectList(lqw);生成的 SQL:
SELECT id, name, age, status FROM user
WHERE status = ? AND age > ? AND name LIKE ?
ORDER BY id DESC2.1 常用条件
| 方法 | SQL |
|---|---|
eq / ne | = / <> |
gt / ge / lt / le | > >= < <= |
like / likeLeft / likeRight | LIKE |
in / notIn | IN / NOT IN |
isNull / isNotNull | IS NULL |
between | BETWEEN |
and / or | 逻辑连接 |
orderByAsc / orderByDesc | 排序 |
动态条件:第一个参数是 boolean
.like(condition, column, value) 的第一个参数为 false 时该条件不拼接。这是 MP 里做动态 SQL 的关键,避免写一堆 <if>。
三、分页插件
MP 分页依赖插件,必须注册 MybatisPlusInterceptor,否则 selectPage 不分页。
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 分页插件(指定数据库类型)
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
// 乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
// 防全表更新删除
interceptor.addInnerInterceptor(new BlockAttackInnerInterceptor());
return interceptor;
}
}Page<User> page = new Page<>(1, 10); // 第 1 页,每页 10 条
userMapper.selectPage(page, new LambdaQueryWrapper<User>().eq(User::getStatus, 1));
System.out.println(page.getTotal()); // 总条数
System.out.println(page.getRecords()); // 当前页数据3.1 分页原理
MP 用 MyBatis 的插件(拦截器)机制拦截 SQL,在 Executor 层做两件事:
深分页优化
LIMIT 1000000, 10 要扫描并丢弃前 100 万行,很慢。改为游标/延迟关联:WHERE id > ? ORDER BY id LIMIT 10,或先查主键再回表。
四、逻辑删除
不真删,只把 deleted 字段置为 1,查询自动加 WHERE deleted = 0。
// 实体字段上标注
@TableLogic
private Integer deleted; // 0 未删,1 已删
// 配置(可选,默认 0/1)
mybatis-plus.global-config.db-config.logic-delete-field: deleted
mybatis-plus.global-config.db-config.logic-delete-value: 1
mybatis-plus.global-config.db-config.logic-not-delete-value: 0执行 deleteById(1) 实际发的是:
UPDATE user SET deleted = 1 WHERE id = 1 AND deleted = 0逻辑删除的三个坑
- 唯一索引冲突:删除后
deleted=1,再插入同名(如 username)会撞唯一索引。方案:唯一索引带上deleted字段,或删除时把deleted设为id。 - 查询必须带条件:手写 SQL 时要记得
AND deleted = 0。 - 数据量只增不减:需定期归档。
五、代码生成器
MyBatis-Plus-Generator 可根据数据库表一键生成 Entity / Mapper / Service / Controller,减少样板代码。注意生成后要人工审查,别直接上生产。
六、字段自动填充
创建时间/更新时间/操作人等公共字段,用 MetaObjectHandler 自动填充,避免每次手动 set。
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
this.strictInsertFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
@Override
public void updateFill(MetaObject metaObject) {
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
}📚 官方文档与参考资料
✏️ 练习与自测
QueryWrapper和LambdaQueryWrapper有什么区别?为什么推荐后者?- MP 分页的底层原理是什么?为什么一定要注册
PaginationInnerInterceptor? - 逻辑删除后为什么可能撞唯一索引?给出两种解决方案。
- 写一段代码:用
LambdaQueryWrapper实现「按状态筛选 + 按年龄倒序 + 分页」的查询。
本节小结
MP 是「增强不改动」:BaseMapper 提供通用 CRUD,LambdaQueryWrapper 用方法引用安全地拼条件,分页必须注册拦截器(改写 SQL 为 COUNT + LIMIT),@TableLogic 逻辑删除、MetaObjectHandler 自动填充。用爽的同时别忘了:深分页要游标优化、逻辑删除注意唯一索引。