目录
- [MyBatis-Plus 系统性知识体系总结](#MyBatis-Plus 系统性知识体系总结)
-
- 一、整体定位与设计理念
-
- [1.1 框架定位](#1.1 框架定位)
- [1.2 核心设计原则](#1.2 核心设计原则)
- 二、核心特性体系
-
- [2.1 通用 CRUD 层封装](#2.1 通用 CRUD 层封装)
-
- [(1)BaseMapper(DAO 层通用接口)](#(1)BaseMapper(DAO 层通用接口))
- (2)IService(业务层通用接口)
- [2.2 主键生成策略](#2.2 主键生成策略)
- [2.3 公共字段自动填充](#2.3 公共字段自动填充)
- [2.4 逻辑删除](#2.4 逻辑删除)
- [2.5 代码生成器](#2.5 代码生成器)
- [2.6 插件化扩展体系](#2.6 插件化扩展体系)
- 三、条件构造器(Wrapper)体系
-
- [3.1 类层级与核心实现](#3.1 类层级与核心实现)
- [3.2 核心使用方式](#3.2 核心使用方式)
-
- (1)QueryWrapper(查询场景)
- (2)UpdateWrapper(更新场景)
- [(3)Lambda 构造器(生产推荐)](#(3)Lambda 构造器(生产推荐))
- [3.3 常用条件方法](#3.3 常用条件方法)
- [3.4 核心优势](#3.4 核心优势)
- 四、分页插件(PaginationInnerInterceptor)
-
- [4.1 插件定位与版本演进](#4.1 插件定位与版本演进)
- [4.2 配置方式(Spring Boot)](#4.2 配置方式(Spring Boot))
- [4.3 基础使用方式](#4.3 基础使用方式)
-
- [(1)Mapper 层分页](#(1)Mapper 层分页)
- [(2)Service 层分页](#(2)Service 层分页)
- [4.4 分页实现原理](#4.4 分页实现原理)
- [4.5 高级特性](#4.5 高级特性)
- [4.6 注意事项](#4.6 注意事项)
- 五、乐观锁插件(OptimisticLockerInnerInterceptor)
-
- [5.1 设计思想与适用场景](#5.1 设计思想与适用场景)
- [5.2 配置与使用步骤](#5.2 配置与使用步骤)
- [5.3 实现机制](#5.3 实现机制)
- [5.4 支持的数据类型](#5.4 支持的数据类型)
- [5.5 限制与注意事项](#5.5 限制与注意事项)
- 六、插件底层架构总览
-
- [6.1 基础依赖:MyBatis 拦截器](#6.1 基础依赖:MyBatis 拦截器)
- [6.2 MP 分层插件架构](#6.2 MP 分层插件架构)
- [6.3 推荐插件执行顺序](#6.3 推荐插件执行顺序)
- 《常见问题排查与最佳实践清单》
-
- [1.1 高频踩坑问题排查](#1.1 高频踩坑问题排查)
-
- (1)分页插件不生效,查询返回全量数据
- (2)乐观锁插件失效,更新不携带版本号条件
- [(3)Lambda 构造器字段映射错误](#(3)Lambda 构造器字段映射错误)
- (4)逻辑删除不生效,查询仍返回已删除数据
- (5)公共字段自动填充不生效
- (6)批量插入性能差
- [1.2 工程最佳实践](#1.2 工程最佳实践)
- 《面试高频考点汇总》
-
- [2.1 基础认知篇](#2.1 基础认知篇)
- [2.2 核心功能篇](#2.2 核心功能篇)
- [2.3 原理机制篇](#2.3 原理机制篇)
- [2.4 实战踩坑篇](#2.4 实战踩坑篇)

MyBatis-Plus 系统性知识体系总结
一、整体定位与设计理念
1.1 框架定位
MyBatis-Plus(简称 MP)是一款MyBatis 增强工具,在 MyBatis 原生能力基础上遵循"只增强、不改变"的原则,对单表 CRUD、条件构建、分页、并发控制等场景做了通用封装,是国内 Java 生态最主流的 ORM 增强框架之一。
1.2 核心设计原则
- 无侵入:不修改 MyBatis 原生架构与 SQL 编写模式,原有项目可平滑接入
- 低损耗:启动阶段完成通用方法注入,运行时无额外性能开销
- 通用性:覆盖 90% 以上单表操作场景,减少重复代码
- 可扩展:插件化架构,支持自定义拦截器与功能扩展
二、核心特性体系
2.1 通用 CRUD 层封装
(1)BaseMapper(DAO 层通用接口)
内置 17+ 基础单表 CRUD 方法,无需编写 XML 映射文件即可完成数据操作:
- 新增:
insert、insertBatch - 删除:
deleteById、deleteBatchIds、deleteByMap、delete - 修改:
updateById、update - 查询:
selectById、selectBatchIds、selectByMap、selectOne、selectList、selectCount、selectPage
(2)IService(业务层通用接口)
在 BaseMapper 之上封装业务语义,提供更丰富的批量操作与链式调用能力:
- 批量操作:
saveBatch、saveOrUpdate、saveOrUpdateBatch - 批量查询:
listByIds、listByMap、list - 分页查询:
page、pageMaps - 链式调用:
query().eq().list()流式编程风格
2.2 主键生成策略
通过 @TableId 注解的 type 属性配置,内置多种主键生成方案:
| 策略类型 | 说明 | 适用场景 |
|---|---|---|
| AUTO | 数据库自增主键 | MySQL、PostgreSQL 等支持自增的数据库 |
| ASSIGN_ID | 雪花算法生成分布式全局唯一 ID(默认策略) | 分布式系统、分库分表场景 |
| ASSIGN_UUID | 生成无中划线的 32 位 UUID | 简单分布式场景 |
| INPUT | 用户手动传入主键值 | 自定义主键生成逻辑 |
| NONE | 无状态,跟随全局配置 | 项目统一策略管控 |
2.3 公共字段自动填充
通过 MetaObjectHandler 接口实现创建时间、更新时间、操作人等公共字段的自动赋值:
- 字段标记:
@TableField(fill = FieldFill.INSERT)(插入时填充)、@TableField(fill = FieldFill.INSERT_UPDATE)(插入更新时填充) - 自定义实现:实现
insertFill和updateFill方法,统一设置字段值
2.4 逻辑删除
内置逻辑删除能力,无需手动编写删除标记更新逻辑:
- 全局配置删除/未删除标识值(如
deleted=1表示删除,0表示未删除) - 实体类字段标注
@TableLogic - 框架自动改写查询、删除、更新 SQL,默认过滤已删除数据
2.5 代码生成器
提供一键式代码生成能力,快速生成项目分层代码:
- 旧版 AutoGenerator:基于 Velocity 模板,配置灵活度高
- 新版 FastAutoGenerator:更简洁的流式 API,支持自定义模板与生成策略
- 生成范围:Entity、Mapper、Service、Controller、XML 映射文件
2.6 插件化扩展体系
基于 MyBatis 拦截器机制,提供可插拔的功能插件,核心包括:
- 分页插件(PaginationInnerInterceptor)
- 乐观锁插件(OptimisticLockerInnerInterceptor)
- 多租户插件(TenantLineInnerInterceptor)
- 动态表名插件(DynamicTableNameInnerInterceptor)
- 性能分析插件(PerformanceInterceptor)
三、条件构造器(Wrapper)体系
3.1 类层级与核心实现
Wrapper 是 MP 动态 SQL 构建的顶层抽象,通过链式 API 拼接 WHERE 条件,核心类继承关系:
Wrapper(顶层接口)
└── AbstractWrapper(抽象基类,实现条件拼接核心逻辑)
├── QueryWrapper:查询条件构造器,支持指定查询字段
│ └── LambdaQueryWrapper:Lambda 形式查询构造器
├── UpdateWrapper:更新条件构造器,支持 SET 字段赋值
│ └── LambdaUpdateWrapper:Lambda 形式更新构造器
└── StringLambdaQueryWrapper:字符串形式 Lambda 构造器
3.2 核心使用方式
(1)QueryWrapper(查询场景)
用于构建 SELECT 语句的 WHERE 条件,支持指定返回字段:
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.select("id", "username")
.eq("status", 1)
.like("name", "张")
.orderByDesc("create_time");
List<User> list = userMapper.selectList(wrapper);
(2)UpdateWrapper(更新场景)
用于构建 UPDATE 语句的 SET 赋值与 WHERE 条件:
UpdateWrapper<User> wrapper = new UpdateWrapper<>();
wrapper.set("status", 0)
.eq("age", 18);
userMapper.update(null, wrapper);
(3)Lambda 构造器(生产推荐)
通过 Lambda 表达式引用实体类字段,编译期校验字段合法性,彻底避免硬编码字段名导致的运行时错误:
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(User::getStatus, 1)
.likeRight(User::getName, "李")
.ge(User::getAge, 20);
3.3 常用条件方法
基础比较类
eq/ne:等于 / 不等于gt/ge:大于 / 大于等于lt/le:小于 / 小于等于between/notBetween:区间范围查询
模糊与包含类
like/notLike:全模糊匹配likeLeft/likeRight:左模糊 / 右模糊匹配in/notIn:集合包含 / 不包含inSql/notInSql:子查询形式 IN 条件
空值与排序类
isNull/isNotNull:字段为空 / 不为空orderByAsc/orderByDesc:升序 / 降序排列orderBy:自定义排序规则
逻辑与嵌套类
and/or:逻辑与 / 逻辑或拼接nested:嵌套条件,生成括号包裹的子条件groupBy/having:分组与分组后筛选
自定义 SQL 扩展
apply:拼接自定义 SQL 片段last:在 SQL 末尾追加自定义内容(如limit 1)exists/notExists:EXISTS 子查询
3.4 核心优势
- 动态 SQL 零配置 :无需在 XML 中编写
<if>标签,Java 代码中灵活拼接条件 - 类型安全:Lambda 构造器编译期检查字段,提前发现错误
- 链式编程:代码简洁易读,条件逻辑清晰
- 条件复用:可封装通用条件,适配多场景查询
四、分页插件(PaginationInnerInterceptor)
4.1 插件定位与版本演进
MP 官方提供的物理分页插件,是最核心的插件之一,基于 MyBatis 拦截器机制实现:
- 3.4.0 之前 :独立
PaginationInterceptor拦截器 - 3.4.0 及之后 :统一纳入
MybatisPlusInterceptor核心拦截器,以PaginationInnerInterceptor内部拦截器形式注册
4.2 配置方式(Spring Boot)
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 添加分页拦截器,指定数据库方言
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
4.3 基础使用方式
(1)Mapper 层分页
// 第1页,每页10条数据
Page<User> page = new Page<>(1, 10);
Page<User> result = userMapper.selectPage(page, queryWrapper);
// 分页结果字段
long total = result.getTotal(); // 总记录数
long pages = result.getPages(); // 总页数
List<User> records = result.getRecords(); // 当前页数据
long current = result.getCurrent(); // 当前页码
long size = result.getSize(); // 每页条数
(2)Service 层分页
Page<User> page = new Page<>(1, 10);
IPage<User> result = userService.page(page, queryWrapper);
4.4 分页实现原理
- SQL 拦截 :拦截 Executor 的
query方法,识别分页参数对象 - 方言适配 :根据配置的数据库类型,生成对应语法的分页 SQL
- MySQL:拼接
LIMIT offset, size - Oracle:通过
ROWNUM嵌套实现分页 - PostgreSQL:拼接
LIMIT size OFFSET offset
- MySQL:拼接
- Count 查询:自动生成并执行 count 统计 SQL,获取总记录数
- 结果封装 :将分页数据与总条数封装为
IPage对象返回
4.5 高级特性
- 溢出处理 :设置
page.setOverflow(true),当当前页大于总页数时,自动返回最后一页数据 - 单页条数限制 :配置
maxLimit参数,限制单页最大查询条数,防止恶意拉取全量数据 - 自定义 Count SQL:复杂多表查询可自定义 count 语句,优化分页性能
- 多方言支持:内置 20+ 数据库方言,覆盖主流关系型数据库
- Count 优化:自动移除不必要的排序与字段,简化 count 语句
4.6 注意事项
- 必须指定正确的
DbType方言,否则生成分页 SQL 语法错误 - 多数据源场景下,需为每个数据源单独配置对应方言
- 复杂 SQL(含子查询、多表复杂关联)自动生成的 count 可能不准确,建议自定义 count
- 插件执行顺序:分页插件需在多租户、动态表名等插件之后注册
五、乐观锁插件(OptimisticLockerInnerInterceptor)
5.1 设计思想与适用场景
(1)核心原理
基于版本号机制实现乐观锁:读取数据时获取当前版本号,更新时将版本号作为 WHERE 条件,若版本号不匹配则更新失败;更新成功则版本号自增 1。
(2)适用场景
- 读多写少、并发冲突概率低的业务(如用户信息更新、订单状态变更)
- 对性能要求高,不希望悲观锁阻塞的场景
- 分布式环境下的轻量级并发控制
5.2 配置与使用步骤
(1)注册插件
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
// 注册乐观锁插件
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
(2)实体类添加版本字段
@Data
public class User {
private Long id;
private String name;
// 版本号字段,标注 @Version 注解
@Version
private Integer version;
}
(3)业务代码使用
直接调用更新方法,插件自动处理版本号逻辑:
User user = userMapper.selectById(1L);
user.setName("新名称");
// 更新时自动携带 version 条件,成功后 version 自动 +1
int rows = userMapper.updateById(user);
// rows = 0 表示更新失败(数据已被其他线程修改)
if (rows == 0) {
// 处理并发冲突:重试、抛出业务异常等
}
5.3 实现机制
- 拦截 update 语句,在 WHERE 条件中追加
version = 当前版本号 - 在 SET 语句中追加
version = version + 1 - 执行更新后,若影响行数为 0,说明数据已被修改,更新失败
- 更新成功时,自动同步实体类中的 version 字段为最新值
5.4 支持的数据类型
- 数值型:
int、Integer、long、Long(推荐使用,性能最佳) - 时间型:
Date、Timestamp、LocalDateTime
5.5 限制与注意事项
- 支持方法有限 :仅对
updateById和update(entity, wrapper)方法生效 - 单版本字段 :一个实体类只能有一个
@Version字段 - 不支持复合主键:复合主键场景下无法正常工作
- 无异常抛出:更新失败不会抛出异常,需通过返回的影响行数判断结果
- 初始值要求:插入数据时版本字段必须有初始值(如 Integer 默认 0)
- Wrapper 不可复用:使用 UpdateWrapper 时,不能复用同一个 wrapper 对象多次更新
六、插件底层架构总览
6.1 基础依赖:MyBatis 拦截器
MP 插件体系基于 MyBatis 的 Interceptor 接口,通过 JDK 动态代理拦截 Executor、StatementHandler 等核心对象的方法,实现 SQL 改写与逻辑增强。
6.2 MP 分层插件架构
3.4.0+ 版本采用核心拦截器 + 内部拦截器的分层设计:
MybatisPlusInterceptor:对外的核心拦截器,负责方法拦截与调度InnerInterceptor:内部功能拦截器接口,分页、乐观锁等均为其实现类- 架构优势:插件可灵活组合、执行顺序可控,避免多插件重复代理
6.3 推荐插件执行顺序
多个内部拦截器按注册顺序执行,推荐优先级:
- 动态表名插件
- 多租户插件
- 分页插件
- 乐观锁插件
《常见问题排查与最佳实践清单》
1.1 高频踩坑问题排查
(1)分页插件不生效,查询返回全量数据
- 常见原因
- 未注册
MybatisPlusInterceptor核心拦截器,或未添加PaginationInnerInterceptor内部拦截器 - 未指定数据库方言
DbType,或方言与实际数据库不匹配 - 分页参数
Page不是 Mapper 方法的第一个参数 - 自定义 SQL 方法名不符合拦截规则,或方法返回值不是
IPage类型 - 多数据源场景下,仅给单个数据源配置了插件
- 未注册
- 解决方案
- 确保拦截器注入 Spring 容器,显式指定
DbType(如DbType.MYSQL) - 分页方法统一将
IPage作为第一个入参,返回值为IPage<T> - 多数据源下,每个
SqlSessionFactory单独加载分页插件
- 确保拦截器注入 Spring 容器,显式指定
(2)乐观锁插件失效,更新不携带版本号条件
- 常见原因
- 实体类版本字段未标注
@Version注解 - 版本字段类型不支持(仅支持数值型、时间型)
- 使用
update(wrapper)重载方法时未传入entity对象 - 复合主键场景下插件无法正常工作
- 复用同一个
UpdateWrapper对象执行多次更新
- 实体类版本字段未标注
- 解决方案
- 数值型字段加
@Version,插入数据时给版本号赋初始值(如0) - 优先使用
updateById方法;使用update(entity, wrapper)时必须传入实体对象 - 每次更新都新建
UpdateWrapper,禁止复用
- 数值型字段加
(3)Lambda 构造器字段映射错误
- 常见原因
- 未开启驼峰下划线转换,或实体字段与数据库列名不匹配
- 字段未加
@TableField显式指定列名,且命名规则不符合默认转换 - 使用了数据库关键字作为字段名
- 解决方案
- 全局配置
map-underscore-to-camel-case: true(默认开启) - 特殊字段通过
@TableField("column_name")手动映射 - 关键字段添加反引号转义,如
@TableField("order")
- 全局配置
(4)逻辑删除不生效,查询仍返回已删除数据
- 常见原因
- 未全局配置逻辑删除标识值,或字段未加
@TableLogic注解 - 自定义 XML SQL 不会被插件自动改写
- 联合唯一索引未包含删除标识字段,导致唯一约束冲突
- 未全局配置逻辑删除标识值,或字段未加
- 解决方案
- 全局配置
logic-delete-value和logic-not-delete-value - 自定义 SQL 需手动拼接删除条件,或使用 MP 提供的条件构造器
- 逻辑删除表的唯一索引需设计为「业务字段 + 删除标识」联合唯一
- 全局配置
(5)公共字段自动填充不生效
- 常见原因
- 实现类未加
@Component注解,未被 Spring 容器管理 - 实体字段未标注
@TableField(fill = FieldFill.INSERT)等填充策略 - 填充字段类型与赋值类型不匹配
- 主键字段无法通过自动填充赋值
- 实现类未加
- 解决方案
- 实现
MetaObjectHandler接口并注入 Spring 容器 - 严格匹配字段类型,日期类统一用
LocalDateTime - 主键赋值使用主键生成策略,不使用自动填充
- 实现
(6)批量插入性能差
- 常见原因
saveBatch默认是逐条执行 INSERT,并非真正的批量 SQL- 未开启 JDBC 批量执行参数
- 解决方案
- JDBC 连接地址添加
rewriteBatchedStatements=true参数 - 大数据量插入时分批提交(如每 1000 条提交一次),避免内存溢出
- JDBC 连接地址添加
1.2 工程最佳实践
编码规范
- 优先使用 Lambda 构造器:避免硬编码字段名,编译期校验字段合法性,降低运行时风险
- 条件复用封装:通用查询条件封装为独立方法,避免重复拼接 Wrapper
- 分层职责清晰:Controller 层不直接操作 Mapper,业务逻辑收敛在 Service 层
- 实体与 DTO 分离:数据库实体不直接作为前端入参/出参,通过 VO/DTO 做数据隔离
性能优化
- 按需查询字段 :使用
select()指定返回字段,禁止select *,减少数据传输与内存占用 - 分页 Count 优化 :复杂多表分页禁用自动 count,通过
setCountSql自定义 count 语句 - 限制单页条数 :全局配置
maxLimit(如 1000),防止恶意请求拉取全量数据拖垮数据库 - 复杂查询手写 SQL:多表关联、子查询等复杂场景,直接在 XML 中编写原生 SQL,不要强行用 Wrapper 拼接
插件配置
- 显式指定方言 :不要依赖自动识别方言,生产环境明确配置
DbType,避免兼容性问题 - 控制插件顺序:多个内部拦截器按「动态表名 → 多租户 → 分页 → 乐观锁」顺序注册
- 按需启用插件:乐观锁、多租户等插件仅在对应业务表使用,不要全项目全局开启
- 溢出处理开启 :分页对象设置
overflow = true,页码超出总页数时自动返回最后一页
业务落地
- 主键策略 :分布式系统统一使用
ASSIGN_ID雪花算法主键,避免自增主键的分库分表扩展问题 - 乐观锁重试:乐观锁更新失败时,根据业务场景实现重试机制(如重试 3 次),而非直接报错
- 逻辑删除配合归档:定期归档已删除的历史数据,避免逻辑删除表数据量过大影响查询性能
- 自动填充统一规范:创建时间、更新时间、创建人、更新人四个公共字段统一自动填充,业务代码不手动赋值
《面试高频考点汇总》
2.1 基础认知篇
- MyBatis-Plus 和 MyBatis、JPA(Hibernate)的区别?
- MyBatis:原生持久层框架,SQL 完全手写,灵活度高但单表操作重复代码多
- MyBatis-Plus:MyBatis 的增强工具,遵循「只增强不改变」原则,封装单表 CRUD、条件构造、分页等通用能力,保留原生 SQL 灵活性,属于半 ORM 框架
- JPA:全自动化 ORM 规范,SQL 对开发者透明,开发效率高但复杂 SQL 优化难度大,适合简单业务场景
- MyBatis-Plus 的核心组件有哪些?
- 基础层:
BaseMapper(DAO 层通用 CRUD)、IService(业务层通用封装) - 条件层:
Wrapper条件构造器体系(Query/Update/Lambda 等实现) - 插件层:分页、乐观锁、多租户、动态表名等内置插件
- 辅助能力:主键生成策略、自动填充、逻辑删除、代码生成器
- 基础层:
- BaseMapper 和 IService 的区别与联系?
- 定位不同:BaseMapper 是 DAO 层接口,面向数据库操作;IService 是业务层接口,面向业务逻辑
- 能力不同:BaseMapper 提供基础单表 CRUD;IService 在其之上封装批量操作、链式查询、saveOrUpdate 等业务语义方法
- 关系:ServiceImpl 实现类中注入 BaseMapper,通过组合方式调用 Mapper 能力
2.2 核心功能篇
- 条件构造器有哪些实现类?各自适用场景?
QueryWrapper:查询场景,支持指定返回字段,硬编码数据库列名UpdateWrapper:更新场景,支持set语句赋值,硬编码列名LambdaQueryWrapper/LambdaUpdateWrapper:生产推荐,通过 Lambda 引用实体字段,编译期校验,避免字段名拼写错误StringLambdaQueryWrapper:支持字符串形式的字段名自动转 Lambda,适用于动态字段场景
- 主键生成策略有哪几种?默认策略是什么?
AUTO:数据库自增主键ASSIGN_ID:默认策略,雪花算法生成分布式全局唯一 IDASSIGN_UUID:生成 32 位无横线 UUIDINPUT:用户手动传入主键值NONE:无状态,跟随全局配置
- 雪花算法的结构与优缺点?
- 结构:1 位符号位 + 41 位时间戳 + 10 位工作机器 ID + 12 位序列号
- 优点:全局唯一、趋势递增、生成性能极高、无第三方依赖
- 缺点:依赖系统时钟,时钟回拨会导致生成重复 ID;机器 ID 分配不当会导致冲突
- 逻辑删除的实现原理?
框架通过拦截器改写 SQL:- 将
DELETE语句改写为UPDATE语句,设置删除标识字段为已删除值 - 查询、更新语句自动拼接
WHERE 删除标识 = 未删除值条件,过滤已删除数据 - 对业务代码完全透明,开发者无需手动处理删除标识
- 将
2.3 原理机制篇
- 分页插件的实现原理?
- 基于 MyBatis
Interceptor机制,拦截Executor.query方法 - 识别方法入参中的
IPage对象,判断是否为分页查询 - 根据配置的数据库方言,生成对应语法的分页 SQL(如 MySQL 的
LIMIT、Oracle 的ROWNUM) - 自动生成并执行
COUNT查询,获取总记录数 - 将查询结果与总条数封装为
IPage对象返回
- 基于 MyBatis
- 乐观锁插件的实现原理?
- 拦截
update语句,在WHERE条件中追加version = 当前版本号 - 在
SET语句中追加version = version + 1 - 执行 SQL 后,若影响行数为 0,说明数据已被其他线程修改,更新失败
- 更新成功时,自动同步实体对象中的版本号为最新值
- 拦截
- 3.4.0 版本之后的插件架构为什么改成「核心拦截器+内部拦截器」?
- 旧版每个功能都是独立的 MyBatis 拦截器,会对同一个方法多次代理,性能损耗大
- 新版外层只有一个
MybatisPlusInterceptor核心拦截器,内部通过InnerInterceptor接口扩展功能 - 优势:统一调度、执行顺序可控、避免重复代理、插件组合更灵活
- BaseMapper 中的通用方法是如何执行的?
- 项目启动时,扫描所有 BaseMapper 的子接口
- 解析实体类的注解信息(表名、字段、主键等),为每个通用方法生成对应的 SQL 语句
- 将生成的 SQL 封装为
MappedStatement注册到 MyBatis 容器中 - 执行时和手写 SQL 一样,走 MyBatis 原生执行流程
2.4 实战踩坑篇
- 分页插件不生效的排查思路?
按优先级依次检查: - 是否注册了
MybatisPlusInterceptor并添加了分页内部拦截器 - 是否正确指定了数据库方言
DbType - 分页参数
Page是否为方法第一个参数 - 方法返回值是否为
IPage类型 - 多数据源下是否每个数据源都配置了插件
- MyBatis-Plus 的局限性有哪些?
- 单表操作能力强,多表复杂关联查询仍需手写 XML SQL
- 分页插件对复杂子查询、多表关联的自动 count 语句可能不准确
- 乐观锁仅支持
updateById和update(entity, wrapper)方法,场景有限 - 默认批量插入并非真正的批量 SQL,需要额外配置 JDBC 参数
- 逻辑删除与唯一索引天然冲突,需要额外设计联合唯一键
- 如何优化 MP 的批量插入性能?
- JDBC 连接串添加
rewriteBatchedStatements=true,驱动会将多条 INSERT 合并为批量执行 - 使用
IService.saveBatch方法,设置合理的批次大小(默认 1000) - 超大数据量分批提交,避免单次事务过大导致数据库压力过高