MyBatis-Plus 通用 CRUD 操作全景指南
MyBatis-Plus 通用 CRUD 操作全景指南
适用版本:3.5.5+(JDK17+)
目标读者:从“会用”到“精通”,一篇文章掌握全部招式
一、为什么需要“通用 CRUD”
- 90% 的业务接口 都是单表增删改查 + 分页 + 批量操作
- 重复 SQL 降低开发效率,增加维护成本
- MyBatis-Plus 把共性逻辑抽象成 无侵入接口,一行代码完成过去 10 行 XML 的工作量
二、技术栈定位
| 层级 | 组件 | 职责 | 是否必须 |
|---|---|---|---|
| Mapper | BaseMapper<T> | 单表原子操作 | ✅ |
| Service | IService<T> + ServiceImpl<M,T> | 组合式、批量化、链式调用 | ✅ |
| 扩展 | Wrapper<T> | 动态条件构造 | ✅ |
三、核心方法总览(思维导图)

四、Mapper 层 API(BaseMapper)
4.1 单条操作
// 插入并返回主键
userMapper.insert(user);// 按主键查 / 删 / 改
User u = userMapper.selectById(1L);
int rows = userMapper.deleteById(1L);
rows = userMapper.updateById(user);
4.2 条件操作
// 按列 map
userMapper.selectByMap(Map.of("name", "Tom"));// 条件构造器
LambdaQueryWrapper<User> wrapper = Wrappers.<User>lambdaQuery().eq(User::getName, "Tom").gt(User::getAge, 18);
List<User> list = userMapper.selectList(wrapper);
Long cnt = userMapper.selectCount(wrapper);
4.3 批量操作
// 批量主键
List<User> users = userMapper.selectBatchIds(Arrays.asList(1L, 2L, 3L));
int delRows = userMapper.deleteBatchIds(Arrays.asList(1L, 2L));// 高性能原生批量插入(推荐)
userMapper.insertBatchSomeColumn(entityList);
五、Service 层 API(IService)
5.1 单条 & 批量
// 单条
userService.save(user);
userService.updateById(user);// 批量
userService.saveBatch(list, 1000); // 每 1000 条提交一次
userService.updateBatchById(list, 500);
userService.saveOrUpdateBatch(list); // 主键存在则更新
5.2 链式调用(不写 Wrapper)
// 查询链
List<User> tomList = userService.query().like(User::getName, "Tom").list();// 更新链
userService.update().set(User::getAge, 20).eq(User::getId, 1L).update();
5.3 分页 & 存在判断
Page<User> page = userService.page(new Page<>(1, 10),Wrappers.<User>lambdaQuery().gt(User::getAge, 18)
);boolean exists = userService.exists(Wrappers.<User>lambdaQuery().eq(User::getName, "Tom")
);
六、分页实战(只需两步)
- 注册插件
@Bean
public MybatisPlusInterceptor interceptor() {MybatisPlusInterceptor mp = new MybatisPlusInterceptor();mp.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));return mp;
}
- 调用
Page<User> page = userMapper.selectPage(new Page<>(2, 20),Wrappers.<User>lambdaQuery().like(User::getName, "T%")
);
七、批量性能调优
| 开关 | 说明 |
|---|---|
rewriteBatchedStatements=true | JDBC 驱动把 N 条 insert 合并成一条 multi-values |
batchSize = 1000 | 每 1000 条 flush 一次,减少内存占用 |
// 1000 条一批,自动分批
userService.saveBatch(list, 1000);
结论先行:
MyBatis-Plus 的批量操作方法 会自动拆分 List 进行分批提交,无需人工拆分。
你只需要传入完整的 List,框架内部会按你指定的 batchSize(或默认 1000)自动切片并分批执行。
✅ 自动拆分的证据(源码级)
1. saveBatch 源码片段
java
复制
// ServiceImpl.java
@Override
@Transactional(rollbackFor = Exception.class)
public boolean saveBatch(Collection<T> entityList, int batchSize) {// 关键:使用 SqlHelper.executeBatch 自动切片return SqlHelper.executeBatch(this.entityClass, this.log, entityList, batchSize,(sqlSession, entity) -> sqlSession.insert(sqlStatement(SqlMethod.INSERT_ONE), entity));
}
2. executeBatch 内部逻辑
java
复制
// SqlHelper.java
public static <E> boolean executeBatch(Class<?> entityClass, Log log,Collection<E> list, int batchSize,BiConsumer<SqlSession, E> consumer) {int size = list.size();int idxLimit = Math.min(batchSize, size); // 自动计算每批数量for (int i = 0; i < size; i++) {consumer.accept(sqlSession, iterator.next());if (i == idxLimit - 1) { // 达到批次阈值时 flushsqlSession.flushStatements();idxLimit = Math.min(idxLimit + batchSize, size);}}return true;
}
🚫 反例:哪些方法不会自动分批?
表格
复制
| 方法 | 是否自动分批 | 备注 |
|---|---|---|
insertBatchSomeColumn | ❌ | 底层使用原生 JDBC 批处理,需手动控制批次 |
| 手写 XML `` | ❌ | 需自己分批或设置 batchSize |
🎯 实战示例
// 自动分批,每 500 条提交一次
userService.saveBatch(userList, 500);// 不指定 batchSize 时,默认 1000
userService.saveBatch(userList);
📌 总结
| 场景 | 是否自动分批 | 备注 |
|---|---|---|
saveBatch / updateBatchById / saveOrUpdateBatch | ✅ | 内置自动切片 |
insertBatchSomeColumn | ❌ | 需手动分批 |
| XML 手写批量 SQL | ❌ | 需手动分批 |
结论:日常使用 Plus 的批量方法,放心把完整 List 丢进去即可。
八、自定义通用方法(扩展点)
- 继承
AbstractMethod - 注册自定义
SqlInjector
public class LogicDeleteBatchByIds extends AbstractMethod {@Overridepublic MappedStatement injectMappedStatement(...) {String sql = String.format("UPDATE %s SET deleted = 1 WHERE id IN (%s)",tableInfo.getTableName(),collectionToString(ids));return addUpdateMappedStatement(mapperClass, modelClass, "logicDeleteBatchByIds", sql);}
}
九、常见坑 & 排查
| 现象 | 排查命令 | 原因 |
|---|---|---|
selectOne 抛 TooManyResults | 日志打印 SQL | 忘记加 limit |
| 分页 total 为 0 | Page 对象未注入插件 | 插件未生效 |
| saveOrUpdate 未生效 | 实体主键为空 | 主键策略未配置 |
十、总结
-
BaseMapper:原子级 API,覆盖 90% 单表需求
-
IService:再封装一次,提供链式、批量化、事务友好
-
Wrapper:让 SQL 像写 Java 一样自然
-
插件体系:分页、乐观锁、多租户…随插随用
| `Page` 对象未注入插件 | 插件未生效 |
| saveOrUpdate 未生效 | 实体主键为空 | 主键策略未配置 |
十、总结
- BaseMapper:原子级 API,覆盖 90% 单表需求
- IService:再封装一次,提供链式、批量化、事务友好
- Wrapper:让 SQL 像写 Java 一样自然
- 插件体系:分页、乐观锁、多租户…随插随用
掌握这 20+ 个方法,即可在日常开发中 “不写 XML,不写 SQL” 完成绝大多数数据库交互。
