1. 引言
MyBatis-Plus(简称 MP)是 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。它内置了通用 Mapper、通用 Service,几乎可以不用写 SQL 就能完成单表 CRUD 操作,同时保留了 MyBatis 的灵活性,支持自定义 SQL、分页插件、乐观锁插件等强大功能。
本文将围绕 MyBatis-Plus 的核心功能展开,重点讲解条件构造器(Wrapper)、自定义 SQL、主键策略、分页插件、乐观锁等核心特性的实现原理与使用方式,帮助读者从原理层面理解 MP 的设计思想。
2. MyBatis-Plus 核心功能总览
MyBatis-Plus 的核心功能可以概括为以下几个方面:
- 通用 CRUD:内置 BaseMapper 与 IService,提供单表增删改查的通用方法,无需编写 SQL。
- 条件构造器(Wrapper):通过面向对象的链式 API 构造查询条件,支持 Lambda 表达式,编译期校验字段名。
- 自定义 SQL:支持在 XML 或注解中编写自定义 SQL,并可与 Wrapper 结合实现动态条件拼接。
- 分页插件:基于 MyBatis 拦截器实现物理分页,自动生成 COUNT 查询。
- 乐观锁插件:通过版本号字段实现乐观锁,自动拼接 SET version = version + 1。
- 主键策略:支持雪花算法、自增、UUID 等多种主键生成策略。
- 逻辑删除:通过全局配置实现逻辑删除,自动拼接 deleted = 0 条件。
- 代码生成器:一键生成 Entity、Mapper、Service、Controller 等全套代码。
整体架构如下图所示:
#mermaid-svg-mtz9XE2DC8kCcDGD{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-mtz9XE2DC8kCcDGD .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-mtz9XE2DC8kCcDGD .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-mtz9XE2DC8kCcDGD .error-icon{fill:#552222;}#mermaid-svg-mtz9XE2DC8kCcDGD .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-mtz9XE2DC8kCcDGD .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-mtz9XE2DC8kCcDGD .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-mtz9XE2DC8kCcDGD .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-mtz9XE2DC8kCcDGD .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-mtz9XE2DC8kCcDGD .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-mtz9XE2DC8kCcDGD .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-mtz9XE2DC8kCcDGD .marker{fill:#333333;stroke:#333333;}#mermaid-svg-mtz9XE2DC8kCcDGD .marker.cross{stroke:#333333;}#mermaid-svg-mtz9XE2DC8kCcDGD svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-mtz9XE2DC8kCcDGD p{margin:0;}#mermaid-svg-mtz9XE2DC8kCcDGD .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-mtz9XE2DC8kCcDGD .cluster-label text{fill:#333;}#mermaid-svg-mtz9XE2DC8kCcDGD .cluster-label span{color:#333;}#mermaid-svg-mtz9XE2DC8kCcDGD .cluster-label span p{background-color:transparent;}#mermaid-svg-mtz9XE2DC8kCcDGD .label text,#mermaid-svg-mtz9XE2DC8kCcDGD span{fill:#333;color:#333;}#mermaid-svg-mtz9XE2DC8kCcDGD .node rect,#mermaid-svg-mtz9XE2DC8kCcDGD .node circle,#mermaid-svg-mtz9XE2DC8kCcDGD .node ellipse,#mermaid-svg-mtz9XE2DC8kCcDGD .node polygon,#mermaid-svg-mtz9XE2DC8kCcDGD .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-mtz9XE2DC8kCcDGD .rough-node .label text,#mermaid-svg-mtz9XE2DC8kCcDGD .node .label text,#mermaid-svg-mtz9XE2DC8kCcDGD .image-shape .label,#mermaid-svg-mtz9XE2DC8kCcDGD .icon-shape .label{text-anchor:middle;}#mermaid-svg-mtz9XE2DC8kCcDGD .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-mtz9XE2DC8kCcDGD .rough-node .label,#mermaid-svg-mtz9XE2DC8kCcDGD .node .label,#mermaid-svg-mtz9XE2DC8kCcDGD .image-shape .label,#mermaid-svg-mtz9XE2DC8kCcDGD .icon-shape .label{text-align:center;}#mermaid-svg-mtz9XE2DC8kCcDGD .node.clickable{cursor:pointer;}#mermaid-svg-mtz9XE2DC8kCcDGD .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-mtz9XE2DC8kCcDGD .arrowheadPath{fill:#333333;}#mermaid-svg-mtz9XE2DC8kCcDGD .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-mtz9XE2DC8kCcDGD .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-mtz9XE2DC8kCcDGD .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-mtz9XE2DC8kCcDGD .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-mtz9XE2DC8kCcDGD .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-mtz9XE2DC8kCcDGD .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-mtz9XE2DC8kCcDGD .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-mtz9XE2DC8kCcDGD .cluster text{fill:#333;}#mermaid-svg-mtz9XE2DC8kCcDGD .cluster span{color:#333;}#mermaid-svg-mtz9XE2DC8kCcDGD div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-mtz9XE2DC8kCcDGD .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-mtz9XE2DC8kCcDGD rect.text{fill:none;stroke-width:0;}#mermaid-svg-mtz9XE2DC8kCcDGD .icon-shape,#mermaid-svg-mtz9XE2DC8kCcDGD .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-mtz9XE2DC8kCcDGD .icon-shape p,#mermaid-svg-mtz9XE2DC8kCcDGD .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-mtz9XE2DC8kCcDGD .icon-shape .label rect,#mermaid-svg-mtz9XE2DC8kCcDGD .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-mtz9XE2DC8kCcDGD .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-mtz9XE2DC8kCcDGD .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-mtz9XE2DC8kCcDGD :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
业务代码
BaseMapper / IService
条件构造器 Wrapper
SQL 解析与拼接
MyBatis 执行器
数据库
分页插件 / 乐观锁插件
3. 条件构造器(Wrapper)原理与使用
3.1 Wrapper 是什么
Wrapper 是 MyBatis-Plus 中用于封装查询条件的核心抽象类,它把 SQL 中的 WHERE、ORDER BY、GROUP BY、HAVING 等子句抽象为 Java 对象,通过链式调用构造查询条件。
Wrapper 的继承体系如下:
Wrapper<T>
├── AbstractWrapper<T>
│ ├── QueryWrapper<T>
│ ├── UpdateWrapper<T>
│ └── LambdaQueryWrapper<T>
│ └── LambdaUpdateWrapper<T>
3.2 核心实现原理
Wrapper 的核心原理是将条件表达式解析为 SQL 片段。以 QueryWrapper 为例,调用 eq("name", "张三") 时,内部会生成一个 MergeSegments 对象,将条件片段按 AND/OR 组合,最终在 getSqlSegment() 方法中拼接为完整的 SQL 片段。
关键源码逻辑如下:
// AbstractWrapper 中的核心方法
public Children eq(R column, Object val) {
return addCondition(true, column, SqlKeyword.EQ, val);
}
private Children addCondition(boolean condition, R column, SqlKeyword keyword, Object val) {
// 将条件封装为 QueryCondition 对象,存入条件列表
queryConditions.add(new QueryCondition(column, keyword, val));
return (Children) this;
}
当执行 selectList(wrapper) 时,MP 会调用 wrapper.getSqlSegment() 生成 WHERE 子句,再通过 TableInfo 将实体字段映射为数据库列名,最终拼装成完整 SQL。
3.3 常用 API 示例
// 查询年龄大于 18 且姓名包含"张"的用户
QueryWrapper<User> wrapper = new QueryWrapper<>();
wrapper.gt("age", 18)
.like("name", "张")
.orderByDesc("create_time");
List<User> users = userMapper.selectList(wrapper);
3.4 Lambda 条件构造器
Lambda 条件构造器通过 LambdaQueryWrapper 使用实体类的 getter 方法引用代替字符串字段名,编译期即可校验字段是否存在,避免运行时因字段名拼写错误导致异常。
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.gt(User::getAge, 18)
.like(User::getName, "张")
.orderByDesc(User::getCreateTime);
List<User> users = userMapper.selectList(wrapper);
Lambda 的实现原理是:通过 SerializedLambda 解析方法引用,提取出对应的属性名,再结合 TableInfo 映射为数据库列名。
4. 自定义 SQL 的实现方式
4.1 为什么需要自定义 SQL
虽然 MP 提供了通用 CRUD,但在多表联查、复杂子查询、特定数据库函数等场景下,仍需要手写 SQL。MP 支持两种自定义 SQL 的方式:注解方式和 XML 方式。
4.2 注解方式
public interface UserMapper extends BaseMapper<User> {
@Select("SELECT * FROM user WHERE age > #{age} AND name LIKE CONCAT('%', #{name}, '%')")
List<User> selectByCondition(@Param("age") Integer age, @Param("name") String name);
}
4.3 XML 方式
<mapper namespace="com.example.mapper.UserMapper">
<select id="selectUserPage" resultType="com.example.entity.User">
SELECT * FROM user
<where>
<if test="name != null and name != ''">
AND name LIKE CONCAT('%', #{name}, '%')
</if>
<if test="age != null">
AND age > #{age}
</if>
</where>
</select>
</mapper>
4.4 自定义 SQL 结合 Wrapper
MP 最强大的特性之一是自定义 SQL 与 Wrapper 结合,可以在自定义 SQL 中动态拼接 Wrapper 生成的 WHERE 条件:
@Select("SELECT * FROM user ${ew.customSqlSegment}")
List<User> selectByWrapper(@Param(Constants.WRAPPER) Wrapper<User> wrapper);
调用时:
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.gt(User::getAge, 18).like(User::getName, "张");
List<User> users = userMapper.selectByWrapper(wrapper);
这里的 ${ew.customSqlSegment} 会被替换为 Wrapper 生成的 WHERE 子句,实现自定义 SQL 与动态条件的完美结合。
5. 主键策略
5.1 主键策略类型
MyBatis-Plus 支持多种主键生成策略,通过 @TableId 注解的 type 属性指定:
| IdType.AUTO | 数据库自增,依赖数据库的自增主键 |
| IdType.INPUT | 用户手动输入,不自动生成 |
| IdType.ASSIGN_ID | 默认策略,使用雪花算法生成分布式 ID |
| IdType.ASSIGN_UUID | 使用 UUID 生成主键 |
| IdType.NONE | 不指定,跟随全局配置 |
5.2 雪花算法原理
雪花算法(Snowflake)是 Twitter 开源的分布式 ID 生成算法,生成的 ID 是一个 64 位的 Long 型整数,结构如下:
| 1 bit 符号位 | 41 bit 时间戳 | 10 bit 机器 ID | 12 bit 序列号 |
- 符号位:固定为 0,保证 ID 为正数。
- 时间戳:毫秒级时间戳,可保证 69 年的使用期限。
- 机器 ID:区分不同机器,支持最多 1024 台机器。
- 序列号:同一毫秒内的递增序列,支持每毫秒生成 4096 个 ID。
// 使用示例
@TableId(type = IdType.ASSIGN_ID)
private Long id;
5.3 全局配置
mybatis-plus:
global-config:
db-config:
id-type: assign_id
6. 分页插件
6.1 分页插件原理
MyBatis-Plus 的分页插件基于 MyBatis 的**拦截器(Interceptor)**机制实现。在执行 SQL 前,拦截器会拦截 Executor.query() 方法,解析出原始 SQL,自动生成 COUNT 查询和分页 SQL。
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}
6.2 分页使用示例
Page<User> page = new Page<>(1, 10); // 第 1 页,每页 10 条
LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
wrapper.gt(User::getAge, 18);
Page<User> result = userMapper.selectPage(page, wrapper);
System.out.println("总记录数:" + result.getTotal());
System.out.println("总页数:" + result.getPages());
System.out.println("当前页数据:" + result.getRecords());
6.3 分页 SQL 生成过程
以 MySQL 为例,原始 SQL 为:
SELECT * FROM user WHERE age > 18
分页插件会生成两条 SQL:
— COUNT 查询
SELECT COUNT(*) FROM user WHERE age > 18
— 分页查询
SELECT * FROM user WHERE age > 18 LIMIT 0, 10
7. 乐观锁插件
7.1 乐观锁原理
乐观锁假设并发冲突较少,在更新时通过版本号判断数据是否被修改。更新前先查询版本号,更新时携带版本号并执行 SET version = version + 1,若版本号不匹配则更新失败。
7.2 配置与使用
// 实体类中增加版本字段
@Version
private Integer version;
@Configuration
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
return interceptor;
}
}
7.3 更新 SQL 生成
执行 updateById(user) 时,MP 自动生成如下 SQL:
UPDATE user SET name = ?, version = version + 1
WHERE id = ? AND version = ?
若更新影响行数为 0,说明版本号已被其他事务修改,需要重试或提示用户。
8. 逻辑删除
8.1 逻辑删除原理
逻辑删除并非真正删除数据,而是通过标记字段(如 deleted)将数据标记为已删除。查询时自动拼接 deleted = 0 条件,删除时自动执行 UPDATE 语句。
8.2 配置与使用
@TableLogic
private Integer deleted;
mybatis-plus:
global-config:
db-config:
logic-delete-field: deleted
logic-delete-value: 1
logic-not-delete-value: 0
执行 deleteById(1L) 时,实际执行的 SQL 为:
UPDATE user SET deleted = 1 WHERE id = 1 AND deleted = 0
9. 总结
MyBatis-Plus 通过封装通用 CRUD、条件构造器、分页插件、乐观锁、逻辑删除等能力,大幅提升了开发效率。其核心设计思想是约定优于配置与只增强不改变:
- 条件构造器将 SQL 条件抽象为 Java 对象,配合 Lambda 表达式实现编译期校验。
- 自定义 SQL保留了 MyBatis 的灵活性,并可与 Wrapper 无缝结合。
- 分页与乐观锁插件基于 MyBatis 拦截器机制,对业务代码零侵入。
- 主键策略通过雪花算法解决了分布式场景下的 ID 生成问题。
掌握这些核心功能的原理,不仅能更高效地使用 MyBatis-Plus,还能在遇到复杂业务场景时灵活扩展,写出更优雅的代码。
网硕互联帮助中心





评论前必须登录!
注册