这篇文章同步更新于微信公众号:
前言
在 Java 后台开发的日常工作里单表增删改查几乎占据了全部业务逻辑。原生 MyBatis 以其灵活性著称。却隐藏着一大堆痛点:
每张表都要手写 XML 或注解 SQL,重复代码层出不穷。
动态查询条件需要自己拼接字符串,容易出现注入风险和括号优先级错误。其实,
分页、软删除、时间戳、乐观锁等通用功能得自己封装。维护成本高,
实体与数据库字段映射、批量操作样板代码冗余。
如果你正为上述问题头疼,那么 MyBatis-Plus就是你所需要的“增删改查脱胎换骨”的利器。这篇文章从基础开始,让你快速了解 MP 的主要能力与基础用法。
一、MyBatis-Plus 解决了什么问题
. MyBatis 的痛点再现
CRUD 重复编写: 每个实体类都需要自己写 insert/update/select/delete;
动态 SQL 拼接麻烦: 手动拼字符串,SQL 注入风险高;
通用功能缺失: 分页、逻辑删除、自动填充等必须自行实现;
映射冗余: 实体与 DB 字段映射重复,维护成本大。
. MyBatis-Plus的定位与价值主张
"MyBatis 的提高工具。只提高不修改"
No‑invasive:原有 XML/注解完全保留,可平滑迁移;怎么说呢,
"开箱即用":CRUD、分页、条件构造器等功能无需手写 SQL;怎么说呢,
"语法糖内置":逻辑删除、自动填充、乐观锁、分布式主键全都已准备好;
"Lambda 安全":使用 Lambda 表达式构造条件,编译期校验字段名避免硬编码错误。话说回来,
. 主要优势聚焦痛点方法
No‑invasive & 可迁移 – 不会破坏已有业务代码;
Coding Efficiency – 单表 CRUD 减少90%
MVC 分层友好 – DAO 与 Service 层职责清晰,降低耦合度;
Avoid Repetition – 自动生成通用查询/更新/分页,无需重复实现。
. 使用场景
管理后台或 CRUD 为主的业务场景;中小型服务可直接使用 MP 开箱即用。
复杂多表联查或报表统计仍可通过自定义 XML 与 MP 共存,以满足业务深度需求。
对性能敏感的项目——MP 的分页插件与游标分页可明显提高大数据列表性能。
安全/多租户环境——MP 提供数据脱敏、多租户隔离等公司级功能,一键启用。
二、主要提高功能拆解
. 强大的 CRUD 封装:BaseMapper + IService 双层持久层
DAO 层:BaseMapper
无论是新增还是查询。都需要手写方法签名和 SQL,这让同一个人维护数百行重复代码变得“心累”。只需让 Mapper 接口继承 BaseMapper。即可自动拥有 insert / selectById / deleteById 等方法,无需任何实现代码。
@Mapper
public interface UserMapper extends BaseMapper{}
再看常见内置方法,
insert: 新增单条记录;
selectById。deleteById,updateById: 按主键操作;
selectList,delete: 条件批量查询/删除;老实说,
selectPage: 分页查询;
"自定义 SQL 方法" : 在 Mapper 中定义抽象方法并配合 XML 或注解实现自定义业务 SQL,与 BaseMapper 方法共存。
Service 层:IService + ServiceImpl
Service 层往往只做简单包装。缺乏统一的批量和链式调用能力,导致业务代码繁琐且难以复用。IService 提供批量操作、链式 Lambda 查询还有语义化方法,让业务层更易读、更易维护。
IService 接口继承: ;(例如 UserService extends IService);
IService 实现类继承 ServiceImpl : 自动注入对应 Mapper,并把通用逻辑封装到父类中。示例这方面,
@Service
public class UserServiceImpl extends ServiceImpl{
}
IService 独有提高能力包括:
批量新增/更新: saveBatch / updateBatchById;
链式 Lambda 查询: lambdaQuery.eq.list;按理说,
至于语义化方法。getById / list / page 等更直观;
至于主键批量操作,listByIds / removeByIds / updateBatchById / saveBatch.
两者区别快速对照表格:
区别对照图示 ✅️🛠️💡🔍
BaseMapper
DAO
底层操作
仅支持单条 CRUD。无批量或链式支持
适用于原始 MyBatis 场景,仅做数据访问层。
IService
Service
业务封装
支持批量 + 链式 + 容错 + 乐观锁
适用于标准分层开发,将 DAO 与 Service 区分开来。
. 条件构造器 Wrapper — 动态拼接 WHERE 的终结者
"Wrapper 是 MP 最主要的语法糖" 。用它能彻底消除传统 MyBatis 动态 SQL 的繁琐拼接,让你专注业务本身而非字符串处理。
- 四大构造器介绍
• QueryWrapper : 查询/删除,字段写字符串;• LambdaQueryWrapper : 查询/删除,Lambda 方法引用;• UpdateWrapper : 更新条件,用字符串字段;• LambdaUpdateWrapper : 更新条件,用 Lambda 安全写法。→ 推荐使用 *Lambda* 构造器,以获得编译校验和重构安全。怎么说呢,
`
lqw = Wrappers.lambdaQuery;LambdaUpdateWrapper luw = Wrappers.lambdaUpdate;怎么说呢,**⚡️ 小贴士**
- **总是优先使用 Lambda**:既能避免硬编码。又能在 IDE 中享受字段补全与重命名安全。
常用条件语法全覆盖
下面整理了一份可以直接复制粘贴到项目中的模板集合,帮助你瞬间摆脱 if 判断嵌套。怎么说呢,
🔸 等值条件
java
// name = "张三" AND age!=,lqw.eq
.ne;说起来,// null 表示忽略此条件
🔸 区间大小条件
java
// age>?
AND age <= ?
// age BETWEEN ? AND ?
lqw.gt
.le
.娱乐ween;
🔸 模糊查询
java
// 姓名包含 “李”
lqw.like;// 手机号以 “138” 开头
lqw.likeRight;
🔸 空值判断
java
// 邮箱为 NULL
lqw.isNull;// 手机号不为空
lqw.isNotNull;
🔸 包含集合
java
// 部门 ID 在 中
lqw.in);
🔸 複雜邏輯與括號
java
// name = 张三 AND
lqw.eq
.and.or.eq);// name = 张三 OR
lqw.eq
.or.eq);**⚡️ 小技巧**
- **只要需要括号** 就一定要使用 `and` 或 `or` 嵌套,否则后面的优先级会导致结果错乱。
condition 动态条件 —— 消除大量 if 嵌套
所有 Wrapper 条件方法第一个参数均为 `boolean condition`:
condition
是否拼接
true
会拼接
false
忽略
这代表着可以轻松应对前端传参中的可选过滤项:
java
String name = "";// 前端传过来的姓名,可为空;Integer minAge = null;// 最小年龄,可为空,// 当有姓名才做模糊搜索,有最小年龄才做范围判断。lqw.like,User::getName,name)
.ge;比起传统的 if 判断嵌套:
java
if) { lqw.like;}
if{ lqw.ge;}
节省了大量 boilerplate 并减少了潜在 bug。
字段查询过滤 —— 精简返回结果
在列表页或接口返回时经常只需要几个字段即可。MP Wrapper 支持 `select` 方法。只返回指定列,从而避免全字段扫描造成的性能浪费。java
LambdaQueryWrapper lq =
Wrappers.lambdaQuery
.select(User::getId。User::name,User :: getPhone)
.eq;List users =
userMapper.selectList;⚠️ **注意**
仅支持 Lambda 写法,以防止硬编码导致字段名失效。
更新构造器 & 主要注意事项
基础使用示例
一次性将部门 ID 为 `1` 的所有使用者年龄统一改为 `25`:
java
LambdaUpdateWrapper luw =
Wrappers.lambdaUpdate
.setIfPresent->v.set) // 若无值则忽略;.eq,userMapper.update;
主要注意事项
空值覆盖风险
普通 set 无判空逻辑,会把 null 覆盖数据库已有值。
使用 setIfPresent 或带 的 set 可自动跳过空值。
updateById 全量覆盖坑
updateById 会把实体中所有字段全部提交。包括 null,所以仅适用于完整实体保存,不适合局部编辑场景。
**常用方法**
- 对于局部动态更新,请优先使用 Wrapper 链式更新。- 避免直接调用 `updateById` 来修改部分字段。
三、分页功能 —— 开箱即用物理分页
前置配置
java
@Configuration
public class MybatisPlusConfig {
@Bean public MybatisPlusInterceptor mpInterceptor{
MybatisPlusInterceptor interceptor=new MybatisPlusInterceptor;老实说,interceptor.addInnerInterceptor(
new PaginationInnerInterceptor);// 根据 DB 类型选择插件
return interceptor;}
}
Mapper 层分页示例
java
Page page = new Page<>;Page result =
userMapper.selectPage(page,Wrappers.lambdaQuery
.eq);List records=result.getRecords;long total=result.getTotal;long pages=result.getPages;
Service 层极简分页
java
Page pageData =
userService.lambdaQuery
.eq
.page);
自定义 SQL 分页——多表联查 & 高级统计必备
MP 分页插件兼容自定义 XML,而且无需手动加 LIMIT 或 COUNT:
1️⃣ 定义 Mapper 方法签名,第一个参数必须是 Page 对象:
xml
SELECT u.id,u.name,u.phone。d.deptname FROM sys user u LEFT JOIN sysdept d ON u.dept id=d.id
WHERE u.deleted=false
AND #{name} IS NOT NULL -- 示例动态过滤 -->
AND u.name LIKE CONCAT
/* no limit/count needed */
int selectUserPage Page,@Param String name);② 调用即可得到分页结果:
java
Page page=new Page<>;Page result=userMapper.selectUserPage;List records=result.getRecords;long total=result.getTotal;✅ **关键说明**
自动拦截计数总数,不必自己实现 Count;
返回 VO 支持任意对象类型;
支持复杂联查、自定义 group by 等高级需求。
四、高阶提高功能一览
功能
配置方式
痛点解决
⚙️ SQL 日志打印
yml 配置 log‑impl 为 StdOutImpl
能实时查看最终执行 SQL 与参数
🚀 慢 SQL 性能监控
PerformanceInnerInterceptor
超时阈值警告日志
🛑 防误操作安全机制
默认禁止无条件 UPDATE/DROP 删除
防止开发者误删全库数据
🕶 数据脱敏插件
注解 @TableField
隐私信息自动脱敏,仅返回遮蔽后内容
🌍 多租户隔离插件
TenantLineInnerInterceptor + TenantLineHandler 实现获取租户 ID 并放入 ThreadLocal
零侵入实现多租户数据隔离
📈 游标分页插件
CursorPaginationInnerInterceptor
对百万级数据列表或导出场景调整性能
五、快速上手流程
1️⃣ 添加依赖 mybatis-plus‑boot‑starter 并去掉原生 MyBatis 启动器。2️⃣ 在 application.yml 配置数据库连接,并开启下划线转驼峰及打印日志。3️⃣ 编写实体类并加上 @TableName/@TableField/@TableLogic 等注解。4️⃣ 将 Mapper 接口继承 BaseMapper 并开启 @MapperScan 扫描。5️⃣ 编写 Service 实现类继承 ServiceImpl,并按需注册分页/乐观锁插件。按理说,6️⃣ 用 Wrapper 构造动态查询。再调用内置 CRUD 完成业务逻辑!
六、小结回顾
MP 把单表 CRUD 重复工作消除掉,让开发人员从书写无意义的 XML 转向真正关注业务逻辑;不过,
BaseMapper+IService 双层架构让 DAO 与 Service 清晰分离。同时提供批量和链式调用能力;
Wrapper 条件构造器与 condition 参数让动态查询成为声明式而非程序化,实现真正意义上的「零 if」;
内置分页/软删/自动填充/乐观锁/主键策略等特性即插即用,明显提高生产力;
高阶插件覆盖日志打印、安全防误删、多租户隔离、大数据游标分页等公司级需求,一键集成无侵入。
如果你还在苦恼于每天敲击几十行相似 CRUD 和复杂字符串拼接。那就试试 MP,你将发现效率翻倍而且代码更易维护!