认识MyBatis-Plus
MyBatis Plus 对 MyBatis 它 Plus 在哪?
首先,MyBatis-Plus 是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
而且,MyBatis-Plus 它是无侵入的,只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
支持的数据库也对 MyBatis 进行了增强,而且它是国人开发的,所以也对国产数据库有所支持
任何能使用 MyBatis 进行增删改查,并且支持标准 SQL 的数据库应该都在 MyBatis-Plus 的支持范围内,具体支持情况如上。
更多特性如下
- 无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑
- 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
- 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
- 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
- 支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题
- 支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作
- 支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )
- 内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置
- 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
- 分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库
- 内置性能分析插件:可输出 SQL 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询
- 内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作
最重要的是 MP 只是 “增强” 而非 “替代”,它完全保留了 MyBatis 的所有特性:
- 仍可编写 Mapper XML,实现复杂联表查询(MP 不处理复杂联表,需原生 MyBatis 补充);
- 仍可使用 MyBatis 的注解(
@Select、@Insert等); - 仍可自定义拦截器、插件等;
- 原有 MyBatis 项目接入 MP 无需修改现有代码,只需引入依赖、配置即可。
这就是向下兼容
如何理解 MyBatis Plus 的优势
无代码生成的 CRUD 封装是 MyBatis Plus 最核心的内容之一,我们都知道,原生 MyBatis 实现单表 CRUD 需要
- 编写 Mapper 接口,定义方法
- 编写 Mapper XML 或者使用注解,都是写 SQL 语句
MyBatis Plus 提供了 BaseMapper<T> 接口,内置了 17+
个常用 CRUD 方法,只需让自定义 Mapper 继承它,无需写任何 SQL
即可实现单表操作。很类似 Hibernate 的感觉
可以看一下如下例子就理解了
1 | // 实体类(对应数据库表 user) |
关键就是那个BaseMapper,它内置方法包括insert、deleteById、deleteBatchIds、updateById、selectById、selectList、selectCount
等,覆盖 90% 以上的单表场景。这样,简单情况下,无需编写任何 XML 或注解
SQL,直接调用方法即可
而且 MyBatis Plus 提供了强大的条件构造器
原生 MyBatis 写动态 SQL 需要在 XML 中用
<if>、<where>、<choose>
等标签,代码繁琐且易出错。
MP 提供了 QueryWrapper(普通条件构造)和
LambdaQueryWrapper(类型安全的 Lambda
构造),用链式调用替代 XML 动态 SQL,可读性和可维护性大幅提升。
1 | LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>(); |
而分页也是 MP 最强大的优势之一了
原生 MyBatis 实现分页需要:
- 手动写分页 SQL(如 MySQL 的
LIMIT、Oracle 的ROWNUM) - 手动查询总条数(额外写
COUNT(*)SQL) - 封装分页结果
MP 提供了分页插件
MybatisPlusInterceptor,只需简单配置,即可实现物理分页,且自动返回总条数。使用体验非常接近于
Hibernate 了
1 | // 配置分页插件(Spring Boot 方式) |
最后提一嘴这个代码生成器
原生 MyBatis 若要生成实体、Mapper、Service、Controller 等代码,需借助第三方工具(如 MyBatis Generator),配置复杂且生成的代码不够灵活。
MP 提供了 AutoGenerator
代码生成器,支持自定义模板、生成规则,可一键生成实体类,Mapper 接口 +
XML,Service 接口 + 实现类,还有额外的 Controller
层,这样,新建表后,几分钟即可生成全套基础代码,避免重复劳动。
其实,我倒是理解为 MP 的核心优势就是再 MyBatis 的基础上,通过一些增强封装,使得 MyBatis 这个半自动 ORM 能够在保持灵活性的基础上,给出类似 Hibernate 的快捷开发的使用体验
伟大
快速开始理解 MyBatis Plus
我们将通过一个简单的 Demo 来阐述 MyBatis-Plus 的强大功能,也是基于 Spring Boot 环境
依赖导入
Spring Boot 自3.5.13开始
可以使用mybatis-plus-spring-boot4的版本
1 | <dependency> |
因为mybatis-plus-spring启动器自带
MyBatis,所以无需重复引入
而且集成 MyBatis-Plus 非常的简单,和 MyBatis 一样,我们仅需要一些简单的配置即可使用 MyBatis-Plus 的强大功能
1 |
|
配置 MyBatis Plus
配置 MyBatis Plus 和 配置 MyBatis 差不多,而且 MP 能够继承 MyBatis 的配置项,只不过多了几个
1 | # MyBatis-Plus 配置 |
MP下如何编写实体类
1 | import com.baomidou.mybatisplus.annotation.*; |
那么,从上面一个例子,我们能发现,MP 下的实体上注解的编写风格一定程度下和 Hibernate 有些相似,其中具体内容,例如相关注解,如下
@TableName("表名"):指定实体类对应的数据库表名,如果实体类名 = 表名,可以不写,如果实体类名 ≠ 表名,必须写@TableId:主键注解,标记实体类的主键字段,必须写在主键 id 上,type主键策略有这样四种类型 说明 使用场景 IdType.AUTO 数据库自增 MySQL 主键 AUTO_INCREMENT IdType.INPUT 手动输入 自己 set 主键值 IdType.ASSIGN_ID 雪花算法(默认) 分布式 ID,Long 类型 IdType.ASSIGN_UUID UUID 字符串主键 @TableField:字段注解,标记实体类属性和数据库表字段的映射关系,当字段名不一致的时候使用这个注解进行关联1
2
private String username;当然,这个注解可以达到一个类似 JPA 的审计功能,能够自动填充 创建 / 更新时间 等审计字段
1
2
3
4
5
6
7// 插入时填充
private LocalDateTime createTime;
// 插入和更新时都填充
private LocalDateTime updateTime;对于
fill的策略FieldFill.INSERT:插入时自动填值FieldFill.UPDATE:更新时自动填值FieldFill.INSERT_UPDATE:插入 + 更新都填但是它必须配合自动填充配置类才能生效
而且,用它也可以进行字段忽略
1
2
private String tempData; // 数据库没有这个字段
@TableLogic:逻辑删除注解,标记逻辑删除字段,删除时不是真删,而是把deleted改成 1,删除时:不是真删,而是把deleted改成 1,查询时自动拼接deleted=01
2
3
private Integer deleted;这个字段也需要进行配置
1
2
3logic-delete-field: deleted
logic-delete-value: 1
logic-not-delete-value: 0@Version:乐观锁,和 Hibernate 一样,标记上了,MP 会自动帮你实现乐观锁。
MyBatis-Plus 的配置类
对于上面提到的审计字段的自动填充,需要编写一个配置类,参考如下
因为 MyBatis-Plus 不知道你要填充什么字段、填充什么值。你必须需要一个自定义配置器来定义自动填充规则。
1 | import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; |
上述配置类,对于如下字段
1
2private LocalDateTime createTime; // 创建时间
private LocalDateTime updateTime; // 更新时间避免了
user.setCreateTime(LocalDateTime.now());的操作,让 MyBatis-Plus 自动帮你赋值,不用手动写。而对于
MetaObjectHandler就是 MP 自动填充的相关接口,实现它获取自动填充的能力strictInsertFill:插入时严格填充(严格填充:只有字段为 null 时才填充,不会覆盖你手动设置的值。)strictUpdateFill:更新时严格填充
那么,上述配置类什么时候生效呢?
实体类字段上加注解,标记需要自动填充的字段和自动填充的时机,然后调用 MP 的方法,在这里是插入和更新,时候就会执行自动填充相关字段
1
2
3
4
5
private LocalDateTime createTime;
private LocalDateTime updateTime;
而对于分页查询,你也需要编写一个配置类来让 MyBatis-Plus 的分页查询功能生效。
1 | import com.baomidou.mybatisplus.annotation.DbType; |
如果不写这个配置类,Page<User> page = new Page<>(1,10);然后userMapper.selectPage(page, null);就不会分页,而是查全表
所以说,MP 的分页功能基于插件机制,不是默认开启的,必须通过插件(拦截器) 实现。
这个插件会拦截你的查询 SQL,自动拼接 LIMIT
关键字,自动计算总条数、总页数,然后只要用到 Page
对象,就会自动分页。
Mapper 层
那么 MyBatis Plus 与 MyBatis 带来的最大差距就是 Mapper 层上的不同
1 | import com.baomidou.mybatisplus.core.mapper.BaseMapper; |
我们看看 BaseMapper 这个接口
- 可以看到注释的内容,Mapper 接口继承 BaseMapper,什么都不用写,不用 XML,不用写 SQL,不用写方法,就几乎能完成单表所有 CRUD,其中方法在BaseMapper 里面人家都写好了,实现了肯定有其功能。
BaseMapper 是 MyBatis-Plus 提供的一个预置接口。它里面已经定义好了所有单表常用的 CRUD 方法,你只要继承它,就等于拥有了全部方法。打开 BaseMapper 源码你会看到。
BaseMapper<User> 泛型为什么必须写?泛型
<User> 的作用
- 告诉 MP 对应的数据库表:MP 通过实体类上的
@TableName("user")找到表名 - 告诉 MP 字段名和类型:通过实体类的
@TableField映射列名
然后,MP 就能自动生成 SQL,不用你写,MP 根据泛型自动拼接 SQL
使用 MyBatis-Plus 开发 Mapper 层,必须继承
BaseMapper<T>,然后涉及到条件的,使用条件构造器
QueryWrapper/LambdaQueryWrapper,这样复杂查询也不用写
SQL,例如
1 | QueryWrapper<User> wrapper = new QueryWrapper<>(); |
Service 层
使用 MyBatis Plus 编写 Service 层的时候也有一些不一样的地方,虽然乍一看没什么
这里只讲比较简单的内容,详细的持久层接口的相关内容可以去文档查看,https://baomidou.com/guides/data-interface/#chain
1 | import com.baomidou.mybatisplus.extension.service.IService; |
上述接口的实现
1 | import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; |
MP 对 Service 业务层 做了极大简化,通过两个接口和一个实现类,CURD方法就几乎不怎么写了,只需要写自定义业务逻辑就可以。
- Mapper 层:继承
BaseMapper就能拥有基础单表 CRUD - Service 接口:继承
IService<T>来定义业务方法 - Service 实现类:继承
ServiceImpl<M, T>就能自动实现 IService 所有方法 + 自定义业务逻辑
Service 接口层的extends IService<User>是 MP
业务层核心接口,这样能自动继承 30
多个通用的业务方法,可以在这里扩展自定义业务方法,我们来看看
IService 里面的源码
接着实现类extends ServiceImpl<UserMapper, User>,这又是什么?它是MP
提供的通用 Service 实现类,它能自动实现 IService
所有方法,你完全不用写,而且比较关键的是ServiceImpl
里自动注入了 baseMapper,等价于
1 |
|
ServiceImpl 的源码也比较特殊,它内部也没有任何内容,也是通过继承和实现接口来实现的功能继承
所以你可以直接用 baseMapper 调用 Mapper
层所有方法。而且在这里,条件构造器也依旧有效,一般情况下我们用LambdaQueryWrapper
而且我们根据上述代码可以看到,IService<T>作为接口,定义能干什么,所以需要
Service 的接口层来继承,如果不继承
IService,你就一个方法都没有,ServiceImpl<M,T>作为模板实现类,已经把方法全部写好了,继承它,你的
Service 层的实现类就能自动实现 IService 接口里的所有方法,还能自动注入
Mapper
为什么不直接实现 IService,非要继承 ServiceImpl?
因为如果你不继承 ServiceImpl,而是自己
implements IService<User>,那不就回来了,一切内容你还要自己写
Controller 层
对于 Controller 层,就没什么太多不一样的了
可以看到,只使用了比较少的代码,无论是 Mapper 中还是 Service 中,实现了各种接口,MP 自己就自动帮你做了很多比较基础和简单的查询内容,而往往这些内容占据了日常开发的很大一部分
1 | import com.baomidou.mybatisplus.core.metadata.IPage; |





