MyBatis-Plus 是什么?它和 MyBatis 是什么关系、核心功能有哪些?
简化版
**MyBatis-Plus(简称 MP)是 MyBatis 的「增强工具」——它不改变 MyBatis 的任何东西(只增强不改变),在 MyBatis 之上加了一层,让「单表 CRUD」几乎零 SQL、大幅减少样板代码。**核心功能:① BaseMapper<T>——你的 Mapper 继承它,就白得 insert/deleteById/updateById/selectById/selectList 等一堆单表 CRUD 方法,不用写一行 SQL 和 XML;② 条件构造器 Wrapper(QueryWrapper/LambdaQueryWrapper)——用链式 API 拼查询条件(.eq("age",18).like("name","张")),不用手写 WHERE;③ IService/ServiceImpl——Service 层也封装了一批批量方法;④ 分页插件——配一个拦截器,selectPage 就自动分页(自动加 LIMIT 和查总数);⑤ 代码生成器、逻辑删除、自动填充(创建/更新时间)、乐观锁、多租户等。关键定位:MP 适合「单表 CRUD 提效」,复杂 SQL 还是照样写 MyBatis 的 XML/注解——MP 和 MyBatis 共存,MP 处理简单的、MyBatis 处理复杂的。
详细版
MyBatis-Plus 核心功能:
| 功能 | 说明 |
|---|---|
| BaseMapper | 继承即得单表 CRUD(无需写 SQL) |
| 条件构造器 Wrapper | 链式拼 WHERE 条件(QueryWrapper/LambdaQueryWrapper) |
| IService/ServiceImpl | Service 层批量方法封装 |
| 分页插件 | selectPage 自动分页(拦截器实现) |
| 逻辑删除 | @TableLogic,删除变更新标志位 |
| 自动填充 | @TableField(fill) 自动填创建/更新时间 |
| 乐观锁 | @Version 自动加版本号条件 |
| 代码生成器 | 根据表生成 Entity/Mapper/Service/Controller |
// ① 实体映射(注解标明表名、主键、字段)
@TableName("user")
public class User {
@TableId(type = IdType.AUTO) private Long id;
private String name;
private Integer age;
@TableLogic private Integer deleted; // 逻辑删除标志
@TableField(fill = FieldFill.INSERT) private LocalDateTime createTime; // 自动填充
}
// ② Mapper 继承 BaseMapper → 白得单表 CRUD
public interface UserMapper extends BaseMapper<User> { }
// ③ 条件构造器 + 分页
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<User>()
.eq(User::getAge, 18)
.like(User::getName, "张")
.orderByDesc(User::getCreateTime);
Page<User> page = userMapper.selectPage(new Page<>(1, 10), wrapper); // 自动分页
⚠️ MyBatis-Plus 的定位是「增强不是替代」,用它要守住边界:它极大提升了「单表 CRUD」的效率(省掉大量重复的 insert/select XML),但复杂查询(多表 join、复杂子查询、聚合)不要硬凑 Wrapper——用 Wrapper 拼复杂 SQL 会写得又长又难读,还不如老老实实写一段 MyBatis 的 XML
<select>。所以最佳实践是「简单单表用 MP 的 BaseMapper/Wrapper,复杂 SQL 回归 MyBatis 的 XML」,两者在同一个 Mapper 里共存(BaseMapper 的方法 + 你自己在 XML 里定义的方法)。另外用Wrapper拼接的字段名如果用字符串(QueryWrapper的.eq("user_name", x))容易写错列名且重构不友好,优先用LambdaQueryWrapper(.eq(User::getName, x),类型安全、重构自动改)。
完整版教学
一、MyBatis-Plus 是什么:增强不是替代
理解 MP 的第一要点:它是 MyBatis 的「增强」,共存而非替代:
MyBatis 的痛点:单表 CRUD 也要写一堆重复代码
每个表都要写:insert、deleteById、updateById、selectById、selectList
→ 几十个表 = 几十份几乎一样的 XML/SQL,重复劳动
MyBatis-Plus 的定位:在 MyBatis 之上"增强"
官方口号:"只增强不改变"——
引入 MP 不影响原有 MyBatis 的任何功能和用法
你原来的 XML、注解 SQL 照样能用
MP 只是"额外"给你一堆开箱即用的能力
关系:
MyBatis-Plus 依赖 MyBatis,是它的超集/增强层
简单单表 → 用 MP 的 BaseMapper/Wrapper(零 SQL)
复杂查询 → 用 MyBatis 的 XML/注解(照旧)
→ 两者在一个项目、一个 Mapper 里和平共存
MP 的定位是「增强不替代」——官方口号「只增强不改变」:引入 MP 不影响原有 MyBatis 功能,你的 XML/注解照样用,MP 只是额外给一堆开箱即用能力。它是 MyBatis 的超集/增强层,解决「单表 CRUD 也要写重复代码」的痛点。简单单表用 MP、复杂查询用 MyBatis,两者共存。理解「MP 是 MyBatis 的增强(只增强不改变)、共存不替代、简单单表用 MP 复杂查询用 MyBatis」,就抓住了 MP 的定位。
二、BaseMapper:单表 CRUD 零 SQL
MP 最核心的能力是 BaseMapper——继承即得单表 CRUD:
public interface UserMapper extends BaseMapper<User> { }
就这一行,白得一堆方法(无需写任何 SQL/XML):
插入:insert(entity)
删除:deleteById(id)、delete(wrapper)、deleteBatchIds(ids)
更新:updateById(entity)、update(entity, wrapper)
查询:selectById(id)、selectBatchIds(ids)、selectOne(wrapper)、
selectList(wrapper)、selectCount(wrapper)、selectPage(page, wrapper)
原理(简化):
MP 在启动时,为继承 BaseMapper 的接口"动态注入"这些方法的 SQL
它知道实体对应哪张表(@TableName)、哪些字段(反射 + 注解)
→ 自动拼出 insert/select 等 SQL,注册成 MappedStatement
→ 相当于"帮你写好了单表 CRUD 的 SQL"
省掉的样板代码:
每个表原来要写的一堆基础 CRUD XML → 全没了
BaseMapper<T> 是 MP 的核心——Mapper 继承它就白得一堆单表 CRUD 方法(insert/deleteById/updateById/selectById/selectList/selectPage 等),无需写 SQL。原理:MP 启动时根据实体的表名(@TableName)和字段(反射+注解)自动拼出 CRUD 的 SQL 并注册成 MappedStatement(相当于帮你写好了单表 SQL)。省掉了每个表重复的基础 CRUD XML。理解「BaseMapper 继承即得单表 CRUD、MP 启动时根据实体自动拼 SQL 注册 MappedStatement、省掉重复样板」,就掌握了 MP 最核心的能力。
三、条件构造器 Wrapper:链式拼条件
查询条件靠「条件构造器 Wrapper」链式拼装,不用手写 WHERE:
QueryWrapper(字符串字段名):
new QueryWrapper<User>()
.eq("age", 18) WHERE age = 18
.like("name", "张") AND name LIKE '%张%'
.gt("create_time", date) AND create_time > ?
.orderByDesc("id") ORDER BY id DESC
LambdaQueryWrapper(Lambda 引用字段,推荐):
new LambdaQueryWrapper<User>()
.eq(User::getAge, 18) ← 用方法引用,不写字符串列名
.like(User::getName, "张")
优点:类型安全、编译期检查、重构改字段名自动同步、防列名写错
常用条件方法:
eq/ne/gt/ge/lt/le(比较)、like/notLike(模糊)、
in/notIn、between、isNull/isNotNull、
and/or(组合)、orderByAsc/Desc、groupBy、having、last
动态条件(条件成立才加):
.eq(age != null, User::getAge, age) ← 第一个参数是布尔,true 才拼这个条件
→ 优雅地处理"参数为空就不加这个条件",替代一堆 if
条件构造器 Wrapper 链式拼查询条件:QueryWrapper(字符串字段名)和 LambdaQueryWrapper(Lambda 方法引用字段,推荐——类型安全、防列名写错、重构自动同步)。常用方法覆盖 eq/gt/like/in/between/orderBy/groupBy 等。动态条件(.eq(条件, 字段, 值) 第一个布尔参数 true 才拼)优雅替代「参数为空就不加条件」的一堆 if。理解「Wrapper 链式拼条件、LambdaQueryWrapper 用方法引用类型安全防写错(推荐)、动态条件.eq(布尔,字段,值)替代 if」,就掌握了条件构造器。
四、分页插件:自动分页
MP 的分页靠「分页插件(拦截器)」实现自动分页:
配置分页插件(MyBatis-Plus 的拦截器):
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
使用(selectPage):
Page<User> page = new Page<>(1, 10); // 第1页,每页10条
userMapper.selectPage(page, wrapper);
→ MP 自动:
① 执行 SELECT COUNT(*) 查总数
② 给原 SQL 加 LIMIT 分页
page.getTotal()、page.getRecords()、page.getPages() 拿结果
原理:分页拦截器(MyBatis 插件机制)
拦截查询,改写 SQL 加上 LIMIT(不同数据库方言不同)
并额外执行 count 查询
→ 这是 MyBatis 插件机制的典型应用(拦截 StatementHandler)
对比手写分页:不用自己拼 LIMIT、不用单独写 count SQL → 省事
MP 的分页靠分页插件(拦截器)——配一个 PaginationInnerInterceptor,selectPage(page, wrapper) 就自动分页(自动执行 COUNT(*) 查总数 + 给 SQL 加 LIMIT)。原理是 MyBatis 的插件机制(拦截查询、改写 SQL 加分页、额外执行 count,不同数据库方言不同)。相比手写分页(自己拼 LIMIT、单独写 count),省事很多。这是 MyBatis 插件机制的典型应用。理解「MP 分页靠分页插件(拦截器)、selectPage 自动 count+加 LIMIT、基于 MyBatis 插件机制改写 SQL」,就掌握了 MP 的分页。
五、实用增强:逻辑删除、自动填充、乐观锁
MP 还封装了一批「常见需求」的注解,免去手动处理:
① 逻辑删除(@TableLogic):
@TableLogic private Integer deleted;
→ deleteById 不真删,而是 UPDATE deleted=1
→ 所有查询自动加 WHERE deleted=0(过滤已删除)
免去手动写"软删除"逻辑
② 自动填充(@TableField(fill=...)):
@TableField(fill = FieldFill.INSERT) private LocalDateTime createTime;
@TableField(fill = FieldFill.INSERT_UPDATE) private LocalDateTime updateTime;
+ 实现 MetaObjectHandler 指定填什么值
→ 插入/更新时自动填创建时间、更新时间、创建人等
免去每次手动 set 时间
③ 乐观锁(@Version):
@Version private Integer version;
→ updateById 自动加 WHERE version=旧值、SET version=version+1
→ 实现乐观锁并发控制,免去手动处理版本号
④ 主键策略(@TableId):AUTO(自增)/ASSIGN_ID(雪花)/INPUT 等
这些把"重复的横切逻辑"(软删除、审计字段、乐观锁)封装成注解
→ 声明式,一个注解搞定,不用每处手写
MP 用注解封装了常见横切需求:① 逻辑删除 @TableLogic(deleteById 变 UPDATE deleted=1、查询自动加 WHERE deleted=0);② 自动填充 @TableField(fill)(插入/更新自动填创建/更新时间,配 MetaObjectHandler);③ 乐观锁 @Version(updateById 自动加版本号条件);④ 主键策略 @TableId(自增/雪花/输入)。这些把「重复的横切逻辑」(软删除、审计字段、乐观锁)封装成声明式注解。理解「MP 注解封装横切:@TableLogic 逻辑删除/@TableField(fill)自动填充时间/@Version 乐观锁/@TableId 主键策略、声明式免手写」,就掌握了 MP 的实用增强。
六、代码生成器与边界
MP 还有代码生成器,但用 MP 要守住「边界」:
代码生成器(AutoGenerator):
连数据库、读表结构 → 自动生成
Entity(含字段/注解)、Mapper、Service、Controller、XML
→ 建好表,一键生成一整套基础代码,进一步减少样板
边界(关键!MP 不是万能的):
✓ MP 擅长:单表 CRUD、简单条件查询 → 用 BaseMapper/Wrapper
✗ MP 不擅长:复杂多表 join、复杂子查询、聚合统计
→ 硬用 Wrapper 拼会又长又难读,还不如写 XML
最佳实践:"简单交给 MP,复杂回归 MyBatis"
同一个 Mapper 里:
继承 BaseMapper(白得单表 CRUD)
+ 自己在 XML/注解里定义复杂查询方法
两者共存,各司其职
其他注意:
- LambdaQueryWrapper 优先于 QueryWrapper(类型安全防列名写错)
- 批量操作注意性能(saveBatch 默认是循环单条,非真批量,
可配 rewriteBatchedStatements 或用真批量方案)
MP 的代码生成器能根据表结构一键生成 Entity/Mapper/Service/Controller/XML 一整套。但用 MP 要守边界:MP 擅长单表 CRUD 和简单条件查询(用 BaseMapper/Wrapper),不擅长复杂多表 join/子查询/聚合(硬用 Wrapper 又长又难读,不如写 XML)。最佳实践「简单交给 MP、复杂回归 MyBatis」(同一 Mapper 继承 BaseMapper + 自定义复杂查询方法共存)。理解「MP 代码生成器一键生成、边界:单表简单查询用 MP 复杂查询用 XML、LambdaWrapper 优先、批量注意性能」,就掌握了 MP 的实际使用边界。
记忆钩子:「MyBatis-Plus(MP)=MyBatis 的增强(只增强不改变、共存不替代);核心:①BaseMapper
继承即得单表 CRUD 零 SQL(MP 启动按实体自动拼 SQL 注册 MappedStatement)②条件构造器 Wrapper 链式拼条件(LambdaQueryWrapper 用方法引用类型安全防列名写错、动态条件.eq(布尔,字段,值)替代 if)③IService 批量④分页插件(拦截器自动 count+加 LIMIT,基于 MyBatis 插件机制)⑤@TableLogic 逻辑删除/@TableField(fill)自动填时间/@Version 乐观锁/@TableId 主键策略⑥代码生成器;边界:单表简单用 MP、复杂 join 回归 MyBatis XML,两者共存」 。
七、常见误区与追问
- 误区:MyBatis-Plus 是替代 MyBatis 的新框架。 不是——MP 是 MyBatis 的增强层(只增强不改变),依赖 MyBatis、和它共存;你原有的 XML/注解 SQL 照样用,MP 只是额外提供单表 CRUD、Wrapper、分页等能力。
- 误区:用了 MP 就不用写任何 SQL 了。 MP 只免除「单表 CRUD 和简单条件查询」的 SQL;复杂多表 join、子查询、聚合统计还是要写 MyBatis 的 XML/注解——硬用 Wrapper 拼复杂 SQL 又长又难读,不如回归 XML。
- 误区:QueryWrapper 和 LambdaQueryWrapper 随便用。 优先 LambdaQueryWrapper——它用方法引用(User::getName)指定字段,类型安全、编译期检查、防列名写错、重构改字段名自动同步;QueryWrapper 用字符串列名(“user_name”)容易写错且重构不友好。
- 误区:MP 的分页是内存分页。 是物理分页——分页插件(拦截器)改写 SQL 加上数据库的 LIMIT,并额外执行 COUNT 查总数;是数据库层面的真分页,不是查全部再内存截取。
- 追问:BaseMapper 的单表 CRUD 方法是怎么来的(没写 SQL 怎么执行)? MP 在启动时为继承 BaseMapper 的接口动态注入这些方法——它根据实体的 @TableName(表名)和字段(反射+注解)自动拼出 insert/select/update/delete 的 SQL,注册成 MyBatis 的 MappedStatement;执行时和普通 Mapper 方法一样走 MyBatis 流程。
- 追问:MP 的逻辑删除是怎么实现的? @TableLogic 标注的字段作为删除标志——调 deleteById 时 MP 不真删,而是执行 UPDATE 把标志位置为「已删除」;同时所有查询会自动追加 WHERE 删除标志=未删除,过滤掉逻辑删除的记录;免去手动写软删除逻辑。
- 追问:MP 的分页/逻辑删除等增强底层用了 MyBatis 的什么机制? 主要是 MyBatis 的插件(拦截器)机制——分页插件拦截查询改写 SQL 加 LIMIT;逻辑删除、乐观锁等通过拦截/改写 SQL 实现;这正是 MyBatis 插件机制(拦截四大对象)的典型应用。
八、加强记忆
MyBatis-Plus(MP)是 MyBatis 的「增强工具」(官方口号「只增强不改变」)——不改变 MyBatis、和它共存,在其上加一层让「单表 CRUD 几乎零 SQL」。核心功能:① BaseMapper<T>(Mapper 继承即得 insert/deleteById/updateById/selectById/selectList/selectPage 等单表 CRUD——MP 启动时根据实体 @TableName+字段自动拼 SQL 注册 MappedStatement);② 条件构造器 Wrapper(链式拼 WHERE,LambdaQueryWrapper 用方法引用类型安全防列名写错、动态条件 .eq(布尔,字段,值) 替代 if);③ IService/ServiceImpl(Service 批量方法);④ 分页插件(拦截器自动 COUNT+加 LIMIT,基于 MyBatis 插件机制改写 SQL,物理分页);⑤ 实用注解(@TableLogic 逻辑删除、@TableField(fill) 自动填创建/更新时间、@Version 乐观锁、@TableId 主键策略);⑥ 代码生成器。边界(关键):MP 擅长单表 CRUD 和简单条件查询,复杂多表 join/子查询/聚合回归 MyBatis 的 XML(硬用 Wrapper 又长又难读)——最佳实践「简单交给 MP、复杂回归 MyBatis」,同一 Mapper 里 BaseMapper + 自定义 XML 方法共存。一句话「MP=MyBatis 增强(只增强不改变共存);BaseMapper 继承即得单表 CRUD 零 SQL、Wrapper 链式拼条件(LambdaWrapper 类型安全)、分页插件自动 LIMIT、@TableLogic/@TableField(fill)/@Version 注解封装横切;边界:单表用 MP、复杂 SQL 回归 XML」。