☰
MyBatis-Plus 从入门到实战:常用功能与核心原理
2026/10/8 7:14:17 网站建设 项目流程

前言

在 Java 后端开发中,MyBatis 是非常常见的持久层框架。

不过实际开发中,我们经常会写大量重复的单表 CRUD,例如:

insert deleteById updateById selectById selectList

这些代码在不同项目中往往都差不多。

MyBatis-Plus,简称 MP,就是在 MyBatis 基础上进行增强的工具。

它的核心理念是:

只做增强,不做改变。

也就是说,简单 CRUD 可以交给 MyBatis-Plus,复杂 SQL 依然可以继续使用 MyBatis XML。

简单理解:

MyBatis-Plus = MyBatis + 通用 CRUD + 条件构造器 + 分页 + 逻辑删除 + 自动填充 + 常用插件

下面主要介绍日常开发中最常使用的一些功能。


一、基本使用

在 Spring Boot 项目中引入 MyBatis-Plus 后,假设有一张用户表:

CREATETABLEuser(idBIGINTPRIMARYKEY,usernameVARCHAR(50),ageINT,statusINT,deletedINTDEFAULT0,versionINTDEFAULT1,create_timeDATETIME,update_timeDATETIME);

对应实体类:

@Data@TableName("user")publicclassUser{@TableIdprivateLongid;privateStringusername;privateIntegerage;privateIntegerstatus;privateIntegerdeleted;privateIntegerversion;privateLocalDateTimecreateTime;privateLocalDateTimeupdateTime;}

常见注解包括:

@TableName 表名映射 @TableId 主键 @TableField 字段映射 @TableLogic 逻辑删除 @Version 乐观锁

二、BaseMapper:简化 CRUD

创建 Mapper:

@MapperpublicinterfaceUserMapperextendsBaseMapper<User>{}

只需要继承:

BaseMapper<User>

就可以直接使用常见 CRUD。

新增

Useruser=newUser();user.setUsername("张三");user.setAge(20);userMapper.insert(user);

根据 ID 查询

Useruser=userMapper.selectById(1L);

修改

Useruser=newUser();user.setId(1L);user.setUsername("李四");userMapper.updateById(user);

删除

userMapper.deleteById(1L);

查询全部

List<User>users=userMapper.selectList(null);

这也是 MyBatis-Plus 最直观的优势:

很多简单 SQL 不需要重复手写。


三、IService

实际项目一般会按照:

Controller ↓ Service ↓ Mapper ↓ Database

这样的结构开发。

Service 可以继承:

publicinterfaceUserServiceextendsIService<User>{}

实现类:

@ServicepublicclassUserServiceImplextendsServiceImpl<UserMapper,User>implementsUserService{}

之后可以直接使用:

userService.save(user);userService.getById(1L);userService.list();userService.removeById(1L);userService.updateById(user);

还有:

saveBatch()saveOrUpdate()page()count()

不过复杂业务还是建议定义语义明确的方法,例如:

freezeUser();cancelOrder();refundOrder();

而不是所有业务都直接调用通用 CRUD。


四、QueryWrapper 条件查询

MyBatis-Plus 中非常常用的一个功能就是 Wrapper。

例如查询:

年龄大于等于 18,并且状态正常的用户。

可以写:

QueryWrapper<User>wrapper=newQueryWrapper<>();wrapper.ge("age",18).eq("status",1);List<User>list=userMapper.selectList(wrapper);

常见条件:

eq = ne != gt > ge >= lt < le <= like LIKE in IN between BETWEEN

还可以:

orderByAsc();orderByDesc();

进行排序。


五、更推荐 LambdaQueryWrapper

普通 QueryWrapper 有一个问题:

wrapper.eq("username","张三");

字段名是字符串。

如果以后字段发生变化,IDE 重构不一定能够发现这里的问题。

因此实际项目中更推荐:

LambdaQueryWrapper<User>wrapper=newLambdaQueryWrapper<>();wrapper.eq(User::getUsername,"张三").ge(User::getAge,18).eq(User::getStatus,1);List<User>list=userMapper.selectList(wrapper);

相比:

"username"

这种写法:

User::getUsername

更加安全,也更方便重构。

所以我一般更推荐:

LambdaQueryWrapper LambdaUpdateWrapper

六、动态查询

Wrapper 很适合开发搜索接口。

例如前端传:

Stringusername;Integerstatus;Integerage;

可以写:

LambdaQueryWrapper<User>wrapper=newLambdaQueryWrapper<>();wrapper.like(StringUtils.hasText(username),User::getUsername,username).eq(status!=null,User::getStatus,status).ge(age!=null,User::getAge,age);

当条件不满足时,对应 SQL 条件不会被拼接。

相比大量:

if(...){wrapper.eq(...);}

代码会更加简洁。


七、LambdaUpdateWrapper

除了查询,Wrapper 也可以用于更新。

例如:

禁用 id 为 1 的用户。

LambdaUpdateWrapper<User>wrapper=newLambdaUpdateWrapper<>();wrapper.eq(User::getId,1L).set(User::getStatus,0);userMapper.update(null,wrapper);

最终 SQL 类似:

UPDATEuserSETstatus=0WHEREid=1;

对于只修改少数字段的情况,这种方式比较方便。


八、分页查询

后台管理系统通常都会使用分页。

配置:

@ConfigurationpublicclassMybatisPlusConfig{@BeanpublicMybatisPlusInterceptormybatisPlusInterceptor(){MybatisPlusInterceptorinterceptor=newMybatisPlusInterceptor();interceptor.addInnerInterceptor(newPaginationInnerInterceptor(DbType.MYSQL));returninterceptor;}}

查询:

Page<User>page=newPage<>(1,10);Page<User>result=userMapper.selectPage(page,null);

其中:

1 当前页 10 每页条数

分页结果可以获取:

result.getRecords();result.getTotal();result.getCurrent();result.getSize();result.getPages();

如果带条件:

LambdaQueryWrapper<User>wrapper=newLambdaQueryWrapper<>();wrapper.eq(User::getStatus,1).orderByDesc(User::getCreateTime);Page<User>result=userMapper.selectPage(newPage<>(1,10),wrapper);

九、逻辑删除

实际项目中,有些数据不能真正删除。

例如用户、文章、订单等数据可能需要保留历史记录。

这时可以使用逻辑删除。

数据库字段:

deleted 0 = 正常 1 = 已删除

实体中:

@TableLogicprivateIntegerdeleted;

执行:

userMapper.deleteById(1L);

表面上是删除,实际 SQL 类似:

UPDATEuserSETdeleted=1WHEREid=1;

之后正常查询时,会自动过滤已经逻辑删除的数据。

需要注意:

逻辑删除和业务状态不是一回事。

例如订单:

待支付 已支付 已取消 已完成

应该使用status表示,而不是使用deleted。


十、自动填充

很多数据库表都有:

create_time update_time

如果每次手动赋值会比较麻烦。

可以使用 MyBatis-Plus 自动填充。

实体:

@TableField(fill=FieldFill.INSERT)privateLocalDateTimecreateTime;@TableField(fill=FieldFill.INSERT_UPDATE)privateLocalDateTimeupdateTime;

然后实现:

@ComponentpublicclassMyMetaObjectHandlerimplementsMetaObjectHandler{@OverridepublicvoidinsertFill(MetaObjectmetaObject){this.strictInsertFill(metaObject,"createTime",LocalDateTime.class,LocalDateTime.now());this.strictInsertFill(metaObject,"updateTime",LocalDateTime.class,LocalDateTime.now());}@OverridepublicvoidupdateFill(MetaObjectmetaObject){this.strictUpdateFill(metaObject,"updateTime",LocalDateTime.class,LocalDateTime.now());}}

除了时间,还可以用于:

createUser updateUser

等公共字段。


十一、乐观锁

假设两个用户同时修改同一条数据。

用户 A 查询到:

version = 1

用户 B 查询时也是:

version = 1

如果两个人同时提交修改,可能发生数据覆盖。

MyBatis-Plus 可以使用乐观锁。

实体:

@VersionprivateIntegerversion;

配置:

interceptor.addInnerInterceptor(newOptimisticLockerInnerInterceptor());

更新时 SQL 思路类似:

UPDATEuserSETusername='新的名字',version=2WHEREid=1ANDversion=1;

如果其他线程已经把版本改成:

version = 2

那么:

version = 1

这个条件就无法匹配,从而发现并发冲突。


十二、MybatisPlusInterceptor

前面的分页、乐观锁,其实都属于 MyBatis-Plus 的插件能力。

核心入口是:

MybatisPlusInterceptor

例如:

MybatisPlusInterceptorinterceptor=newMybatisPlusInterceptor();interceptor.addInnerInterceptor(newPaginationInnerInterceptor());interceptor.addInnerInterceptor(newOptimisticLockerInnerInterceptor());

除了这些,还可以实现:

多租户 动态表名 数据权限 防止全表更新和删除

等功能。

因此 MyBatis-Plus 不只是简单地帮我们少写 CRUD,也提供了很多数据库访问层的公共能力。


十三、BaseMapper 为什么没有实现类?

我们只写:

publicinterfaceUserMapperextendsBaseMapper<User>{}

却没有:

UserMapperImpl

为什么可以执行?

这里可以简单理解为两个部分。

1. MyBatis 动态代理

MyBatis 会在运行时为 Mapper 接口创建代理对象。

所以:

userMapper.selectById(1L);

实际上调用的是 MyBatis 生成的代理对象。

2. MyBatis-Plus SQL Injector

MyBatis-Plus 在启动时会把:

insert deleteById updateById selectById selectList

这些通用 CRUD 注册进去。

整体流程可以简单理解为:

UserMapper ↓ BaseMapper ↓ MyBatis-Plus 注册通用 SQL ↓ MyBatis Mapper 动态代理 ↓ Executor ↓ JDBC ↓ 数据库

所以 MyBatis-Plus 底层依然建立在 MyBatis 之上。


十四、复杂 SQL 应该怎么办?

使用 MyBatis-Plus 之后,一个常见误区是:

是不是以后都不用写 SQL 了?

答案当然不是。

例如:

SELECT*FROMuserWHEREstatus=1;

这种简单查询非常适合 Wrapper。

但是如果涉及:

多表 JOIN GROUP BY 复杂聚合 子查询 统计报表 窗口函数

如果强行使用 Wrapper 拼接,可读性可能反而下降。

这时候直接使用:

Mapper + XML

通常更加清晰。

所以我个人更推荐:

简单 CRUD ↓ BaseMapper / IService 动态条件 ↓ LambdaQueryWrapper 动态更新 ↓ LambdaUpdateWrapper 复杂 SQL ↓ Mapper XML

十五、实际开发中的几个注意点

1. 尽量使用 Lambda Wrapper

少写:

wrapper.eq("username",username);

推荐:

wrapper.eq(User::getUsername,username);

可以减少字段名写错的问题。


2. 不要直接相信前端传来的 SQL 字段

例如:

wrapper.orderByAsc(sortField);

如果sortField完全由前端控制,就存在安全风险。

更合理的方式是做字段白名单:

username createTime id

只允许指定字段参与排序。


3. 不要滥用 saveOrUpdate

saveOrUpdate(user);

确实方便。

但是很多业务应该明确区分:

新增 修改

例如:

createUser();updateUser();

业务语义会更加清晰。


4. Wrapper 太复杂时直接写 SQL

如果 Wrapper 已经写成:

.and(...).or(...).nested(...).exists(...)

并且自己都很难一眼看懂最终 SQL,那么通常直接写 XML 会更加合适。

工具是为了降低复杂度,而不是增加复杂度。


十六、总结

MyBatis-Plus 本质上是对 MyBatis 的增强。

它比较适合解决:

CRUD 分页 动态查询 逻辑删除 自动填充 乐观锁

这些大量重复出现的开发需求。

常用学习路线可以概括为:

BaseMapper ↓ IService ↓ LambdaQueryWrapper ↓ LambdaUpdateWrapper ↓ 分页 ↓ 逻辑删除 ↓ 自动填充 ↓ 乐观锁 ↓ MybatisPlusInterceptor

但 MyBatis-Plus 并不是为了完全取代 SQL。

比较合理的使用方式应该是:

简单 CRUD 交给 MyBatis-Plus 复杂 SQL 交给 MyBatis 业务逻辑 放在 Service 数据一致性 依靠数据库设计和事务

我认为 MyBatis-Plus 最大的价值不是:

让我们以后不用写 SQL。

而是:

让我们不用再重复写那些没有必要手写的 SQL。

理解这一点之后,MyBatis-Plus 才真正算是用对了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询