摘要:MyBatis-Plus 是基于 MyBatis 框架的一个增强工具,主要目的是简化 MyBatis 的开发过程,提供更加简洁、方便的 CRUD 操作。
目录
[TOC]
MyBatis Plus
MyBatis-Plus 是基于 MyBatis 框架的一个增强工具,主要目的是简化 MyBatis 的开发过程,提供更加简洁、方便的 CRUD 操作。
- 在保留 MyBatis 强大功能的基础上,通过封装和优化一些常见操作来提高开发效率。
功能特性
提供了许多开箱即用的功能,部分核心特性包括:
- 无侵入设计:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑。不会改变 MyBatis 原有的 API 和使用方式,可以自由选择 MyBatis 和 MyBatis-Plus 的功能。
- 强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求
- 自动生成 CRUD 代码:通过
BaseMapper和ServiceImpl接口,提供了一系列 CRUD 操作的方法,如insert、delete、update和select,减少了重复的 SQL 编写工作。
- 自动生成 CRUD 代码:通过
- 条件构造器:如
QueryWrapper,可以通过链式编程方式轻松构建复杂的查询条件。 - 分页查询、性能优化、以及支持多种数据。
- 损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作
- 支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错
- 支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 - Sequence),可自由配置,完美解决主键问题
- 内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用
- 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询
- 分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库
参考:mybatis plus 常用知识汇总(保姆级教程!~)
快速开始
引入依赖
在 pom.xml 文件中,引入相关依赖。
- 相比来说,将
mybatis-spring-boot-starter替换成mybatis-plus-boot-starter。
1 | |
Java 配置类
创建 Application.java 类,配置 @MapperScan 注解,扫描对应 Mapper 接口所在的包路径。
1 | |
应用配置文件
resources/application.yaml 配置文件。
- 将
mybatis替换成mybatis-plus配置项目。实际上,如果老项目在用mybatis-spring-boot-starter的话,直接将mybatis修改成mybatis-plus即可。 - 相比
mybatis配置项来说,mybatis-plus增加了更多配置项,也因此无需在配置mybatis-config.xml配置文件。更多的 MyBatis-Plus 配置项,可以看看 MyBatis-Plus 使用配置 。 - 配置
logging的原因是,方便看到 MyBatis-Plus 自动生成的 SQL 。生产环境下,记得关闭。
1 | |
自动生成
创建数据库表
三种方式不用纠结,对于使用的人来说不分优劣,只分好用不好用。
-
AutoGenerator:官方 Java 配置代码。可以快速生成
Entity、Mapper、Mapper XML、Service、Controller等各个模块的代码,极大的提升了开发效率。-
自定义模板,使用的是Velocity模板引擎(也可使用Freemarker)
-
这里讲解的是新版 (mybatis-plus 3.5.1+版本),旧版不兼容
-
-
MyBatis Plus 插件:简洁易用
-
MyBatisX-Generator 插件 -
Mybatis-plus Code Generator 插件
-
EasyCode 插件,最全面
AutoGenerator
自带的,通过 Java 程序自动生成。
1 | |
生成方式
代码生成器目前支持两种生成方式:
- DefaultQuery (元数据查询)
- 优点: 根据通用接口读取数据库元数据相关信息,对数据库通用性较好。
- 缺点: 依赖数据库厂商驱动实现。
- 备注: 默认方式,部分类型处理可能不理想。
SQLQuery (SQL查询)- 优点: 需要根据数据库编写对应表、主键、字段获取等查询语句。
- 缺点: 通用性不强,同数据库厂商不同版本可能会存在兼容问题(例如,H2数据库只支持1.X版本)。
- 备注: 后期不再维护。
如果是已知数据库(无版本兼容问题),请继续按照原有的SQL查询方式继续使用,示例代码如下:
1 | |
元数据查询目前有如下问题:
-
不支持使用 NotLike 的方式反向生成表。
-
无法读取表注释,解决方法:
- MySQL链接增加属性
remarks=true&useInformationSchema=true - Oracle链接增加属性
remarks=true或者remarksReporting=true(某些驱动版本) - SqlServer:驱动不支持
- MySQL链接增加属性
-
部分 PostgreSQL 类型处理不佳(如 json、jsonb、uuid、xml、money 类型),解决方法:
- 转换成自定义的类型配合自定义 TypeHandler 来处理。
- 扩展 typeConvertHandler 来处理(3.5.3.3 后增加了 typeName 获取)。
-
MySQL 下 tinyint 字段转换问题:
-
当字段长度为 1 时,无法转换成 Boolean 字段,建议在指定数据库连接时添加
&tinyInt1isBit=true。 -
当字段长度大于 1 时,默认转换成 Byte,如果想继续转换成 Integer,可使用如下代码:
1
2
3
4
5
6
7
8
9
10FastAutoGenerator.create("url", "username", "password") .dataSourceConfig(builder -> builder.typeConvertHandler((globalConfig, typeRegistry, metaInfo) -> { // 兼容旧版本转换成Integer if (JdbcType.TINYINT == metaInfo.getJdbcType()) { return DbColumnType.INTEGER; } return typeRegistry.getColumnType(metaInfo); }) );
-
依赖
由于代码生成器用到了模板引擎,请自行引入喜好的模板引擎。
MyBatis-Plus Generator 支持如下模板引擎:
- VelocityTemplateEngine(Default)
- FreemarkerTemplateEngine
- BeetlTemplateEngine
- EnjoyTemplateEngine
如果还想使用或适配其他模板引擎,可自行继承 AbstractTemplateEngine 并参考其他模板引擎实现自定义。
1 | |
使用
可以通过以下两种形式使用代码生成器。
快速生成
在 CodeGenerator 中的 main 方法中直接添加生成器代码,并进行相关配置,然后直接运行即可生成代码。
1 | |
交互式生成
交互式生成在运行之后,会提示输入相应的内容,等待配置输入完整之后就自动生成相关代码。
如果需要更多例子可查看 test 包下面的 samples。
配置
请移步至 代码生成器配置 查看。
BaseDO 类
- baseEntity:用来写一些公共字段。例如:create_time(创建时间)、creator(创建人)、update_time、updator、logic_delete 等。
- baseController:用来写基础公共的控制。
1 | |
BaseMapper
MyBatisPlus 插件
选中Other菜单,会出现Config Database(配置数据库)和Code Generator(代码生成)
- 生成 Controller、Service、Entity
MyBatisX-Generator 插件
- mapper 和 xml 可以来回跳转
- mybatis.xml、mapper.xml 提示
- mapper 和 xml 支持类似 jpa 的自动提示(参考 MybatisCodeHelperPro)
- 集成 mybatis 生成器 GUI(从免费的 mybatis 插件复制)
- 直接用数据库连接,不用另外配置。
- Lombok 注解不是特别友好
MyBatisplus Code Generator
Mybatisplus 代码生成器,介绍了各种代码生成器的优点。本着约定大于配置的理念。
关键词:Mybatis Plus、Maven、Spring Boot、Lombok、Mysql、Freemarker、XMind、Excel 等。
提供两种形式:Windows 桌面工具 exe 和 IDEA 插件。
打开方式:工具 -> Mybatisplus 代码生成器 或 Ctrl + Alt + 0。
功能:
- 一键生成 Mybatis Plus 代码,傻瓜式功能选择。
- Freemarker 代码自定义模板配置。
- 工程化:支持 Maven 和 Spring Boot 工程化生成。
- 多数据源管理。
- 配置可以是数据持久化,用户操作记忆。
- 一键导出数据库表的思维导图。
- 一键导出 Excel 设计文档到数据库表。
EasyCode 插件
EasyCode是基于IntelliJ IDEA Ultimate版开发的一个代码生成插件,主要通过自定义模板(基于velocity)来生成各种想要的代码。
- 通常用于生成Entity、Dao、Service、Controller。
-
如果动手能力强还可以用于生成HTML、JS、PHP等代码。理论上来说只要是与数据有关的代码都是可以生成的。
- 直接用数据库连接,不用另外配置。
- 自定义模板:点击 File->Settings->Easy Code->Template Setting。
- 比如想在生成的 dao 层代码中,额外添加一个不需要任何条件,获取所有数据的getAll()方法(默认的生成模版中没有这个方法)。
- 需要编写自定义模版。
- 支持导出配置。
实体类
- 实体类放在
dal.dataobject包下,以 DO 结尾;mapper 数据库访问类放在dal.mysql包下,以 Mapper 结尾。
BaseDO
BaseDO是所有数据库实体的父类:
abstract class:createTime+creator字段,创建人相关信息。updater+updateTime字段,创建人相关信息。- 增加了
deleted字段,并添加了@TableLogic注解,设置该字段为逻辑删除的标记。 - LocalDateTime 日期时间
1 | |
对应的 SQL 字段如下:
1 | |
UserDO
创建 DO/UserDO.java 类 。相比 「2.5 UserDO」 来说,主要差别有:
extends BaeDO:继承基类- 实体类的注释要完整,特别是哪些字段是关联(外键)、枚举、冗余等。
1 | |
对应的创建表的 SQL 如下:
1 | |
ProductSkuDO
- property:DO 静态类,对应 SQL
properties varchar(512) null comment '属性数组,JSON 格式',
1 | |
自动映射规则
主要涉及如何将数据库表和字段自动映射到 Java 实体类及其属性。
@TableName:表名与实体类名的映射@TableId:主键的自动映射@TableField:字段名与属性名的映射
@TableName
增加了 @TableName 注解:设置了 UserDO 对应的表名是 user 。毕竟,要使用 MyBatis-Plus 给自动生成 CRUD 操作。
-
默认规则:MyBatis-Plus 默认使用实体类名作为数据库表名的前缀。比如,如果你的实体类名为
User,那么它会映射到名为user的数据库表。 -
自定义规则:可以使用
@TableName注解来指定自定义的表名。例如:1
2
3
4@TableName("sys_user") public class User { // 属性和方法 }
@KeySequence
在数据库设计中,主键的生成方式多种多样,而序列(Sequence)是一种常见的生成主键的方式。
用于标识实体类中的主键字段,并指定使用哪个数据库的序列来生成主键。
-
通过在实体类字段上添加
@KeySequence注解,可以简单地实现基于序列的主键生成,无需手动处理序列的获取和使用。 -
value属性: 用于指定使用的数据库序列的名称。
1 | |
@TableId
主键
id 主键编号,推荐使用 Long 型自增,原因是:
- 自增,保证数据库是按顺序写入,性能更加优秀。
- Long 型,避免未来业务增长,超过 Int 范围。
对应的 SQL 字段如下:
1 | |
项目的 id 默认采用数据库自增的策略,如果希望使用 Snowflake 雪花算法,可以修改 application.yaml 配置文件,将配置项 mybatis-plus.global-config.db-config.id-type 修改为 ASSIGN_ID。
@TableId
@TableId:专门用在主键上的注解,如果数据库中的主键字段名和实体中的属性名,不一样且不是驼峰之类的对应关系。
- 可以在实体中表示主键的属性上加@Tableid注解,并指定value属性值为表中主键的字段名,即可以对应上。
在 MyBatis-Plus 中,@TableId 注解用于标识实体类中的主键字段。
- 有两个主要属性:
value和type。分别用于指定字段名和主键生成策略。
@TableId 注解属性
-
value:-
用途:指定数据库表中的主键列名。它的值应该是数据库表中实际的列名。
-
示例:如果数据库表中的主键列名是
user_id,则在实体类中可以这样配置:1
21
2@TableId(value = "user_id", type = IdType.ASSIGN_UUID) private String userId;
-
-
type:-
用途:指定主键生成策略。MyBatis-Plus 提供了多种主键生成策略,
IdType枚举类定义了这些策略。 -
常用的生成策略:
IdType.AUTO:数据库自动生成(通常是自增长 ID)。IdType.INPUT:用户输入 ID(即需要手动设置)。IdType.ASSIGN_ID:由 MyBatis-Plus 生成的 ID(通常是 UUID)。IdType.ASSIGN_UUID:生成 UUID(字符串类型的唯一 ID)。
-
示例:如果想使用 UUID 作为主键,可以使用
IdType.ASSIGN_UUID:1
21
2@TableId(value = "user_id", type = IdType.ASSIGN_UUID) private String userId;
-
@TableField
表字段填充,is fill
-
默认规则:字段名和属性名默认是直接映射的。例如,数据库中的
user_name字段会映射到实体类中的userName属性。 -
驼峰命名规则:MyBatis-Plus 默认启用了驼峰命名转换。即,数据库中的下划线命名(如
user_name)会被自动转换为 Java 属性的驼峰命名(如userName)。 -
自定义字段映射:可以使用
@TableField注解来指定自定义的字段名。例如:1
2@TableField("user_name") private String userName;
@TableField的其他用法:
| 值 | 描述 | 备注 |
|---|---|---|
| value | 数据表中的字段名 | 驼峰命名方式,该值可无 |
| update | 预处理 set 字段自定义注入 | |
| condition | 预处理 WHERE 实体条件自定义运算规则 | |
| el | 详看注释说明 | |
| exist | 是否为数据库表字段 | 默认 true 存在,false 不存在 |
| strategy | 字段验证 | 默认 非 null 判断,查看 com.baomidou.mybatisplus.enums.FieldStrategy |
| fill | 字段自动填充标记 | FieldFill, 配合自动填充使用 。DEFAULT、INSERT、UPDATE、INSERT_UPDATE。 |
value 属性字段映射
比如说数据库中字段为last_name,而实体类的属性为lastName。
前提是在全局策略配置中将驼峰命名关闭。
1 | |
- 关于MyBatisPlus中进行通用CRUD全局策略配置参照:https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/details/89425049
这时就可以在实体类上添加:
1 | |
sql语句的关键词
如果表中字段存在 sql语句的关键词 ,比如 desc , 那么需要按照下面的写法, 否则mybatis plus 拼接含有 desc字段 的sql语句时会报错。
1 | |
select = false
@TableField(select = false)
作用:用于指定某个字段在执行 SQL 查询时不参与查询,即在 SELECT 语句中不包含该字段。
用途:通常用于那些只需要在数据库操作中存在,但不需要在查询结果中显示的字段。
- 例如,逻辑删除字段、内部使用的字段等。
1 | |
exist 属性
@TableField(exist = false)
作用:用于指定某个字段不对应数据库中的任何字段,即在数据库表中不存在该字段。
用途:通常用于那些只在 Java 实体中存在的字段,但在数据库表中没有相应的字段。例如,用于计算或临时存储的数据。
1 | |
又比如在实体类中有一个属性为remark,但是在数据库中没有这个字段,
但是在执行插入操作时,给实体类的remark属性赋值了,那么可以通过在实体类的remark属性上添加:
1 | |
fill 属性自动填充
DefaultDBFieldHandler (opens new window)基于 MyBatis 自动填充机制,实现 BaseDO 通用字段的自动设置。

typeHandler 字段类型处理器
MyBatis Plus 提供 TypeHandler 字段类型处理器,用于 JavaType 与 JdbcType 之间的转换。
@Builder:- 使用时,需要设置实体的
@TableName注解的@autoResultMap = true。 @TableField
复杂字段类型转换
常用的字段类型处理器有:
- JacksonTypeHandler:通用的 Jackson 实现 JSON 字段类型处理器。如 JavaBean 转为 JSON。

字段加密
EncryptTypeHandler (opens new window),基于 Hutool AES (opens new window)实现字段的加密与解密。
例如说,数据源配置 (opens new window)的 password 密码需要实现加密存储,则只需要在该字段上添加 EncryptTypeHandler 处理器。
示例代码如下:
1 | |
另外,在 application.yaml 配置文件中,可使用 mybatis-plus.encryptor.password 设置加密密钥。
字段加密后,只允许使用精准匹配,无法使用模糊匹配。
1 | |
逻辑删除
所有表通过 deleted 字段来实现逻辑删除,值为 0 表示未删除,值为 1 表示已删除。
-
可见
application.yaml配置文件的logic-delete-value: 1和logic-not-delete-value: 0配置项。当然,也可以通过注解的value和delval来定义未删除和删除。 -
具体关于 MyBatis-Plus 的逻辑删除功能,看下 逻辑删除 部分的文档。

自动拼接 WHERE deleted = 0
① 所有 SELECT 查询,都会自动拼接 WHERE deleted = 0 查询条件,过滤已经删除的记录。
- 如果要 SELECT 被删除的记录,只能通过在 XML 或者
@SELECT来手写 SQL 语句。例如:
1 | |
额外增加 delete_time 字段
② 建立唯一索引时,需要额外增加 delete_time 字段(初始值为0,表示未被逻辑删除),添加到唯一索引字段中,避免唯一索引冲突。例如说,system_users 使用 username 作为唯一索引:
- 未添加前:先逻辑删除了一条
username = yudao的记录,然后又插入了一条username = yudao的记录时,会报索引冲突的异常。 - 已添加后:先逻辑删除了一条
username = yudao的记录并更新delete_time为当前时间,然后又插入一条username = yudao并且delete_time为 0 的记录,不会导致唯一索引冲突。
1 | |
Easy-Trans 数据翻译
easy-trans是一款用于做数据翻译的代码辅助插件,利用mybatis plus/jpa/等ORM框架的能力自动查表,让开发者可以快速的把id/字典码 翻译为前端需要展示的数据;
- 能减少sql的注入。
为什么实现
{@link TransPojo}接口?
- 因为使用 Easy-Trans
TransType.SIMPLE模式,集成 MyBatis Plus 查询。
适用场景
-
我有一个id,但是需要给客户展示他的 title/name 但是又不想自己手动做表关联查询
-
我有一个字典码 sex 和 一个字典值0 希望能翻译成 男 给客户展示。
-
我有一组 user id 比如 1,2,3 希望能展示成 张三,李四,王五 给客户
-
我有一个枚举,枚举里有一个title字段,想给前端展示title的值 给客户
-
我有一个唯一键(比如手机号,身份证号码,但是非其他表id字段),但是需要给客户展示他的title/name 但是又不想自己手动做表关联查询
easy trans 支持的五种类型
-
字典翻译(
TransType.DICTIONARY):需要使用者把字典信息刷新到DictionaryTransService 中进行缓存,使用字典翻译的时候取缓存数据源 - 简单翻译(
TransType.SIMPLE):比如有userId需要userName或者userPo给前端,原理是组件使用MybatisPlus/JPA的API自动进行查询,把结果放到TransMap中。- 无需自己实现数据源(推荐),适用于根据id翻译name/title等 。此数据源需要配合
easy_trans_mybatis_plus_extend或者easy_trans_jpa_extend一起使用。
- 无需自己实现数据源(推荐),适用于根据id翻译name/title等 。此数据源需要配合
-
跨微服务翻译(
TransType.RPC):比如订单和用户是2个微服务,但是要在订单详情里展示订单的创建人的用户名,需要用到RPC翻译。- 原理是订单微服务使用restTemplate调用用户服务的一个统一的接口,把需要翻译的id传过去,
- 然后用户微服务使用MybatisPlus/JPA的API自动进行查询把结果给订单微服务,然后订单微服务拿到数据后进行翻译。
- 当然使用者只是需要一个注解,这些事情都是由组件自动完成的。
-
自动翻译(
TransType.AUTO):还是id翻译name场景,但是使用者如果想组件调用自己写的方法而不通过Mybatis Plus/JPA 的API进行数据查询,就可以使用AutoTrans。 - 枚举翻译
(TransType.ENUM):比如要把SEX.BOY 翻译为男,可以用枚举翻译。
依赖
1 | |
配置
1 | |
用法
- 实现 AutoTransable 接口
- 实现 TransPojo 接口:代表这个类需要被翻译或者被当作翻译的数据源。
- 在需要翻译的字段上添加 @Trans 注解即可。
AutoTransable 接口
只有实现了这个接口的才能自动翻译。
为什么要赋值粘贴到 -common 包下?
- 因为 AutoTransable 属于 easy-trans-service 下,无法方便的在 -module-xxx-api 模块下使用
1 | |
@Trans 注解
@Trans(type = TransType.SIMPLE,target = Users.class,fields = "userName")
-
type表示翻译类型 – 简单翻译
-
target表示要翻译出来的结果字段在哪个表中(对应的实体类)
-
fields表示对应翻译的是哪个字段
-
ref—-将翻译出来的数据映射到本实体类的某个属性上
refs—-将翻译出来的数据映射到本实体类的多个属性上
alias—-别名,解决翻译结果字段名重名问题
准备一张设备表device和一张用户表users,其中device表中的 user_id 字段关联了users表中的id字段。
- 这里的target表示将
userId翻译为Users表的field字段"userName", "phone"。
1 | |
User 表
1 | |
@TransMethodResult
@TransMethodResult 注解:用于将翻译结果映射到结果集中。
- 一般情况下,由于easy-trans框架是将结果集映射到前端的,所以当后端需要得到结果集进行查询,导出等操作时值为null,所以需要在调用方法时就将结果映射,需要使用该接口。
1 | |
查询结果
如下:
- 发现userId已经成功地被翻译成了userName和phone,并将翻译的结果封装在了transMap中。

平级模式
如果想让userName、phone在json中和userId同级展示,可以使用平铺模式:
在application.xml中开局平铺模式
1 | |
- 此时再看结果,发现 userName、phone和userId是同级
Mapper 接口
- 默认配置下,MyBatis Mapper XML 需要写在各
yudao-module-xxx-server模块的resources/mapper目录下。 - 简单的单表查询,优先在 Mapper 中通过
default方法实现。 - 不要在 Controller、Service 中,直接进行 MyBatis Plus 操作。建议封装到对应的 Mapper 中,这样会更加简洁干净可管理。否则会导致:
- 会导致 Service 中的代码越来越乱,无法聚焦业务逻辑。逻辑里遍布了各种查询,无法统一管理实际有哪些查询条件。
- Service 会存在很多相同且重复的 SELECT 查询逻辑,无法更好的实现 SELECT 查询的复用。
- Mapper 的 SELECT 查询方法的命名,采用 Spring Data 的 “Query methods” (opens new window)策略,方法名使用
selectBy查询条件规则。
| 示例 | |
|---|---|
| 错误 | ![]() |
| 正确 | ![]() |
BaseMapper 接口
在 cn.iocoder.mybatis.mapper 包路径下,创建 UserMapper 接口。
UserMapper (通过继承 BaseMapper<User>),可以自动生成常规的 CRUD 操作,立即拥有了所有的 CRUD 操作方法。可以在服务层中直接使用这些方法,无需再编写任何 SQL 语句。
-
另外,BaseMapperX (opens new window)接口,继承 MyBatis Plus 的 BaseMapper 接口,提供更强的 CRUD 操作能力。
-
Mybatis Plus 通过 lambda 表达式获取数据库对应的列名。如,
UserDO::getName。
更多 BaseMapper 提供的接口方法,可看看 《MyBatis-Plus 文档 —— CRUD 接口》 。
BaseMapper 提供的常用方法有:
- 四个 CRUD 方法:
#insert(UserDO user)#updateById(UserDO user)#deleteById(@Param("id") Integer id)#selectById(@Param("id") Integer id):
- 条件查询:
QueryWrapper 是 MyBatis-Plus 提供的一个工具类,用于构建查询条件。
selectList方法根据条件查询所有符合条件的记录。
1 | |
BaseMapperX 接口
在 MyBatis Plus 的 BaseMapper 的基础上拓展,提供更多的能力。
封装的方法有:
- selectPage()
- selectJoinPage()
- selecctOne()
- selectFirstOne()
- selectCount()
- selectList()
- insertBatch():批量插入,适合大量数据插入
- updateBatch()
- delete()
- deleteBatch()
如,selectOne 方法,使用指定条件,查询单条记录。
- 在
BaseMapperX中封装QueryWrapper; MemberUserMapper继承BaseMapperX;
1 | |
常见条件查询有:
SelectOne
- 对于
#selectByUsername(@Param("username") String username)方法,使用了QueryWrapper<T>构造相对灵活的条件,这样一些动态 SQL 就无需在 XML 中编写。- 建议 1 :使用 QueryWrapper 拼接动态条件(如用
#selectList(Wrapper<T> queryWrapper)等方法)。 - 建议 2 :因为 QueryWrapper 暂时不支持一些类似
<if />等 MyBatis 的 OGNL 表达式,可以通过继承 QueryWrapper 类,封装 QueryWrapperX 类。 - 更多
QueryWrapper提供的拼接方法,可以看 《MyBatis-Plus 文档 —— 条件构造器》 。
- 建议 1 :使用 QueryWrapper 拼接动态条件(如用
- 对于
#selectPageByCreateTime(IPage<UserDO> page, @Param("createTime") Date createTime)方法,是额外添加的,用于演示 MyBatis-Plus 提供的分页插件。- 更多 IPage 的内容,可以看 《MyBatis-Plus 文档 —— 分页插件》 。
1 | |
UserMapper.xml
在 resources/mapper 路径下,创建 UserMapper.xml 配置文件。
- 是不是一下子,瘦了!
1 | |
简单测试
创建 UserMapperTest 测试类,来测试一下简单的 UserMapper 的每个操作。
- 多了一个分页的单元测试方法。
1 | |
selectCount
#selectCount(...) (opens new window)方法,使用指定条件,查询记录的数量。

selectList
#selectList(...) (opens new window)方法,使用指定条件,查询多条记录。

selectByIds
select(List)ByIds
- 通过 Collection ids 获取 List。
1 | |
selectPage 分页
见下
insertBatch
#insertBatch(...)方法,遍历数组,逐条插入数据库中,适合少量数据插入,或者对性能要求不高的场景。
为什么不使用 insertBatchSomeColumn 批量插入?
- 只支持 MySQL 数据库。其它 Oracle 等数据库使用会报错,可见 InsertBatchSomeColumn (opens new window)说明。
- 未支持多租户。插入数据库时,多租户字段不会进行自动赋值。

批量插入
绝大多数场景下,推荐使用 MyBatis Plus 提供的 IService 的 #saveBatch() (opens new window)方法。示例 PermissionServiceImpl如下:
- XxxBatchInsertMapper

MPJBaseMapper 连表 JOIN
MyBatis Plus Join 的基础接口,提供连表 Join 能力。
1 | |
多表查询
尽量避免数据库的连表(多表)查询,而是采用多次查询 + Java 内存拼接的方式替代。
1 | |

条件构造器
Mybatis Plus 通过 lambda 表达式获取数据库对应的列名。如,
UserDO::getName。
MyBatis-Plus 提供了强大的条件构造器,使得在查询数据库时可以灵活地构建条件,而无需手动编写复杂的 SQL 语句。
- 主要通过
Wrapper接口、及其常用实现类QueryWrapper和LambdaQueryWrapper来实现条件查询。
Wrapper 接口
Wrapper 是 MyBatis-Plus 提供的条件构造器接口,用于构建动态 SQL。
- 有多个实现类,其中最常用的是
QueryWrapper和LambdaQueryWrapper。 - 常用于复杂查询,比如 selectPage 查询方法。
QueryWrapper
QueryWrapper 是 MyBatis-Plus 提供的一个通用条件构造器,用于以非 Lambda 表达式的方式构建查询条件。
常用方法:
- eq: 等于
- ne: 不等于
- gt、ge: 大于、大于等于
- lt、le: 小于、 小于等于
- in: 在指定范围内
- inSql: 允许使用子查询的结果集作为 IN 条件的范围
- between: 在两者之间
- like: 模糊查询
- or、and: 或、并且条件
- isNull、isNotNull: 判断字段是否为 NULL
- orderByAsc、orderByDesc: 升序排序、 降序排序
示例:
1 | |
LambdaQueryWrapper
LambdaQueryWrapper 是 QueryWrapper 的 Lambda 版本,用于在构建条件时避免使用字符串来指定字段,增加了类型安全性。
- 使用字段的 Lambda 表达式来构建条件。
- 通过方法引用的方式来使用实体字段名,避免直接写数据库表字段名时的错写名字。
三种方式:
LambdaQueryWrapper<T>方式QueryWrapper<实体>().lambda()方式Wrappers.<实体>lambdaQuery()方式
示例:
1 | |

LambdaQueryWrapperX
继承 MyBatis Plus 的条件构造器,拓展了 LambdaQueryWrapperX (opens new window)和 QueryWrapperX (opens new window)类(重写父类方法,方便链式调用)。
- 主要是增加 xxxIfPresent 方法,用于判断值不存在的时候,不要拼接到条件中。
apply():- setSql():
1 | |

具体的使用示例如下:

MPJLambdaWrapper
Join QueryWrapper
UpdateWrapper 和 LambdaUpdateWrapper
这两个类分别是用于构建更新条件的构造器,功能与 QueryWrapper 和 LambdaQueryWrapper 类似,但用于 UPDATE 操作。
示例:
1 | |
具体用法可参考 Mybatis plus 官网

分页实现
- 前端:基于 Element UI 分页组件 Pagination(opens new window)
- 后端:基于 MyBatis Plus 分页功能,二次封装。
以 [系统管理 -> 租户管理 -> 租户列表] 菜单为例子,讲解它的分页 + 搜索的实现。
前端分页实现
Vue 界面
界面 tenant/index.vue (opens new window)相关的代码如下:
1 | |
API 请求
请求 system/tenant.js (opens new window)相关的代码如下:
1 | |
后端分页实现
后端:基于 MyBatis Plus 分页功能,二次封装。
Controller 接口
在 TenantController (opens new window)类中,定义 /admin-api/system/tenant/page 接口。
- Request 分页请求,使用 TenantPageReqVO (opens new window)类,它继承 PageParam 类
- Response 分页结果,使用 PageResult 类,每一项是 TenantRespVO (opens new window)类
1 | |
分页参数 PageParam
分页请求,需要继承 PageParam (opens new window)类。
1 | |
分页请求 VO
分页请求VO,分页条件,在子类中进行定义。以 TenantPageReqVO 举例:
1 | |
分页结果 PageResult
分页结果 PageResult (opens new window)类,代码如下:
- 分页结果的数据
list的每一项,通过自定义 VO 类,例如说 TenantRespVO (opens new window)类。
1 | |
Mapper
针对 MyBatis Plus 分页查询的二次分装,在 BaseMapperX 中实现,目的是使用项目自己的分页封装:
- 【入参】查询前,将项目的分页参数 PageParam,转换成 MyBatis Plus 的 IPage 对象。
- 【出参】查询后,将 MyBatis Plus 的分页结果 IPage,转换成项目的分页结果 PageResult。
在 BaseMapperX 中:

具体的使用示例,可见 TenantMapper类中,定义 selectPage 查询方法。
- 完整实战,可见 《开发指南 —— 分页实现》 文档。
- 常用 LambdaQueryWrapperX 实现。
1 | |
IPage
IPage<实体>转 IPage<Vo>
1 | |
高级查询
基于 userMapper.selectMaps(queryWrapper)。
分组查询
示例 :按 age 分组并统计人数
假设想统计各个年龄段的人数,可以使用如下代码:
1 | |
生成的 SQL:
1 | |
聚合查询
示例 :按 age 分组并过滤统计结果(HAVING)
如果只想统计人数大于 1 的年龄段,可以添加 HAVING 条件:
1 | |
生成的 SQL:
1 | |
排序查询
示例 1:按多字段排序,并指定是否为空
假设想按 age 升序排序,并希望将 name 为空的记录排在前面,可以使用如下代码:
1 | |
生成的 SQL:
1 | |
示例 2:按 age 升序和 name 降序组合排序
假设想先按年龄升序排序,再按姓名降序排序,可以使用如下代码:
1 | |
生成的 SQL:
1 | |
逻辑查询
func 方法是 MyBatis-Plus 提供的一个非常灵活的功能,它允许将一段自定义的逻辑包装到查询条件中。
- 这对于需要根据不同的条件来动态构建查询的场景特别有用。
1. func 方法的基本用法
func 方法接收一个 Consumer,参数是 QueryWrapper(或 LambdaQueryWrapper)的一个实例。
- 可以在这个
Consumer中编写自定义的逻辑,并根据不同的条件来动态地添加或修改查询条件。
语法结构:
1 | |
主要参数:
Consumer<QueryWrapper>或Consumer<LambdaQueryWrapper>:这是一个函数式接口,允许传入一个 Lambda 表达式或方法引用。可以在这个接口的accept方法中实现自己的逻辑。
实际应用场景:
-
假设有一个用户查询接口,允许用户根据不同的条件来过滤结果,例如按
id或按name。 -
可以使用
func来根据用户输入动态地构建查询条件。
1 | |
- 示例解释:
- 如果
userInput非空且有效,则查询条件为name = userInput.getName()。 - 否则,查询条件为
id != 1。
- 如果
2. and 和 or 的使用
在 MyBatis-Plus 中,and 和 or 用于在构建查询条件时处理多条件的逻辑运算。它们允许在查询中组合多个条件,以实现复杂的查询逻辑。
and 方法
and 方法:用于将多个查询条件通过逻辑“与” (AND) 连接在一起。将多个条件组合成一个大的 AND 条件,从而要求所有这些条件都必须满足。
示例
假设有一个 User 表,想查询年龄大于 20 且名字为 “Jack” 的用户。可以使用以下代码:
1 | |
生成的 SQL:
1 | |
在这个例子中,and 方法的作用是将 .gt("age", 20) 和 .eq("name", "Jack") 这两个条件通过 AND 组合在一起。
or 方法
or 方法:用于将多个查询条件通过逻辑“或” (OR) 连接在一起。它将多个条件组合成一个大的 OR 条件,只要其中一个条件满足,就会返回符合的结果。
示例
假设有一个 User 表,想查询年龄大于 20 或名字为 “Jack” 的用户。可以使用以下代码:
1 | |
生成的 SQL:
1 | |
在这个例子中,or 方法的作用是将 .gt("age", 20) 和 .eq("name", "Jack") 这两个条件通过 OR 组合在一起。
组合使用 and 和 or
还可以结合使用 and 和 or 方法,以构建更复杂的查询。
- 例如,如果你想查询年龄大于 20 且(名字为 “Jack” 或邮箱为 “test@example.com”)的用户,可以使用以下代码:
1 | |
生成的 SQL:
1 | |
在这个例子中,and 和 or 方法的结合使用允许在 age > 20 的基础上,增加一个组合条件 (name = 'Jack' OR email = 'test@example.com')。
其他查询
apply 方法
apply 方法:允许直接在 QueryWrapper 中插入自定义的 SQL 片段。会被添加到 WHERE 子句的末尾。这允许在现有的查询条件基础上,添加更复杂的条件或函数。
基本用法:
1 | |
在这个示例中:
apply方法接受一个 SQL 片段作为第一个参数,并可以通过{}占位符来插入参数。- 这里使用了
DATE_FORMAT函数来格式化create_time字段,并将其与特定的日期进行比较。
last 方法
last 方法:用于在生成的 SQL 查询的末尾添加额外的 SQL 片段。
- 通常用于添加额外的 SQL 语句,如
ORDER BY,LIMIT,OFFSET等,这些操作是在生成的 SQL 的最后部分进行的。
基本用法:
1 | |
在这个示例中:
last方法添加了一个LIMIT 5子句到查询的末尾,用于限制结果集的返回行数。
示例:假设有一个 User 表,并且想要查询所有 age 大于 20 的用户,并且结果按照 id 降序排列,并且只返回前 10 条记录。
- 可以使用
apply和last方法来实现这个需求:
1 | |
服务层
ServiceImpl 和 IService 是 MyBatis-Plus 中用于服务层(Service Layer)的两个重要接口和类,它们帮助简化和规范了与数据库交互的业务逻辑。
IService 接口
IService 是 MyBatis-Plus 提供的一个通用服务接口。
- 定义了一些常见的 CRUD(Create, Read, Update, Delete)操作,并将这些操作抽象成方法。
- 这意味着,当使用
IService接口时,无需自己手动编写这些常见的数据库操作方法。
一些常用方法:
boolean save(T entity): 保存一个实体类对象到数据库。boolean removeById(Serializable id): 根据 ID 删除数据。boolean updateById(T entity): 根据 ID 更新数据。T getById(Serializable id): 根据 ID 查询数据。List<T> list(): 查询所有数据。Page<T> page(Page<T> page): 分页查询数据。
ServiceImpl 类
ServiceImpl 是 MyBatis-Plus 提供的一个基础实现类,实现了 IService 接口中的方法。
ServiceImpl通常是被继承的,提供了具体的数据库操作方法的实现。- 开发者只需在自己定义的服务实现类中继承
ServiceImpl类,就可以获得默认的 CRUD 功能。
假设有一个用户表 User,并为其定义了一个实体类 User 和一个 Mapper 接口 UserMapper。
- 可以定义一个服务接口
UserService和一个服务实现类UserServiceImpl。
定义服务接口:
1 | |
定义服务实现类:
1 | |
在这个例子中,UserServiceImpl 继承了 ServiceImpl<UserMapper, User> 并实现了 UserService 接口。
- 通过这种方式,
UserServiceImpl类可以直接使用ServiceImpl提供的基本 CRUD 方法。
Mapper 和 Service 中有很多的方法,具体用法可以参考 Mybatis plus 官网

事务处理
在使用注解定义 SQL 查询时,事务管理通常在服务层进行。
- 可以使用 Spring 的
@Transactional注解来管理事务。
1 | |
MapStruct
简介
选型
MyBtatis从数据库中查询的数据映射到domain的实体类上,有时候需要将domain的实体类映射给前端的VO类,用于展示。
因此,可以借助框架或是工具来实现对象的转换,例如说:
- Hutool 里的
BeanUtils.copyProperties():- 但是只能转换类中字段名和类型都一样的字段。
- 而且 由于采用的是反射,实际上当重复调用时效率比较低。(实际测试在生成 次数为1000000时需要1.6秒,而使用MapStruct仅需要69毫秒)。
- Spring BeanUtils
- Apache BeanUtils
- Dozer
- Orika
- MapStruct:通过创建一个 MapStruct Mapper 接口,并定义一个转换接口方法,后续交给 MapStruct 自动生成对象转换的代码即可。
- ModelMapper
- JMapper
MapStruct
MapStruct解决的问题:手动创建bean映射器非常耗时。 该库可以自动生成Bean映射器类。
MapStruct是一个开源的基于Java的代码生成器,用于创建实现 Java Bean之间转换的扩展映射器。
- 使用 MapStruct,只需要(通过
@Mapper、@Mapping注解)创建接口,而该库会通过注解在编译过程中自动创建具体的映射实现, - 大大减少了通常需要手工编写的样板代码的数量。
用于各个对象实体间的相互转换。
- 例如数据库底层实体 转为页面对象,Model 转为 DTO,DTO 转为其他中间对象、VO 等等,相关转换代码为编译时自动产生的新文件和代码。
- 大部分属性都是相同的,只有少部分的不同。
- 两个对象之间相同属性名的会被自动转换。指定特殊情况时,需要通过注解在抽象方法上说明不同属性之间的转换。
转换方法一般均为抽象方法,所以这一类文件一般使用 接口类,或者抽象类均可,官方的介绍一般均使用了接口类文件来完成。
优点
- 使用纯 Java 方法代替 Java 反射机制快速执行。
- 编译时类型安全:只能映射彼此的对象和属性,不能映射一个 Order 实体到一个 Customer DTO 中等等。
- 如果无法映射实体或属性,则在编译时清除错误报告。
依赖
1 | |
映射
基本映射用法
加入 MapStruct 的转换相关的注解。
Convert接口类上加@Mapper:当前类认为是要执行 MapStruct 相关操作的类。标记这个接口作为一个映射接口,并且是编译时MapStruct处理器的入口。- 注意这里定义的是抽象类,实际上使用接口类也可以。
- 在接口中定义了
convert()、toDto()方法:该方法接收一个Doctor实例为参数,并返回一个DoctorDto实例。MapStruct 会把Doctor实例映射到一个DoctorDto实例。- 默认情况下,当源对象与 目标对象拥有一样的属性时会自动转换。
- 通过添加参数,MapStruct 会自动在返回的 POJO 实例中加入。
- 名称不一样时,在方法上加
@Mapping:为指定某些特殊映射的注解,source为入参,源对象的属性名,target为目标对象的属性。二者没有顺序之分。
- 通过
XxxMapper.INSTANCE.入口调用方法:如果要将Doctor实例映射到一个DoctorDto实例,可以这样写:
1 | |
@Mapper
@Mapping
qualifiedByName属性,可以自定义转换方法。
1 | |
Convert 接口
1 | |
多个源类
@Mapping 注解还支持多个对象转换为一个对象。示例如下图:

构建
当构建/编译应用程序时,MapStruct插件会识别出DoctorMapper接口并为其生成一个实现类。
- 这段代码中创建了一个
DoctorMapper类型的实例INSTANCE,在生成对应的实现代码后,这就是我们调用的“入口”。INSTANCE:是为了在外面调用该方法, 接口中的属性默认为静态属性所以可以直接调用到。- 自动生成的接口的实现可以通过Mapper的class对象获取。按照惯例,接口中会声明一个成员变量INSTANCE,从而让客户端可以访问Mapper接口的实现。
1 | |
DoctorMapperImpl类中包含一个toDto()方法,将Doctor属性值映射到DoctorDto的属性字段中。
注意:可能注意到了上面实现代码中的DoctorDtoBuilder。因为builder代码往往比较长,为了简洁起见,这里省略了builder模式的实现代码。
- 如果类中包含Builder,MapStruct会尝试使用它来创建实例;
- 如果没有的话,MapStruct将通过
new关键字进行实例化
子对象映射
多数情况下,POJO中不会只包含基本数据类型,其中往往会包含其它类。比如说,一个Doctor类中会有多个患者类。
- 通过
@Mapping注解指定。
1 | |
更新现有实例
有时,我们希望用DTO的最新值更新一个模型中的属性,对目标对象(例子中是DoctorDto)使用@MappingTarget注解,就可以更新现有的实例。
数据类型转换
数据类型映射
MapStruct支持source和target属性之间的数据类型转换。还提供了基本类型及其相应的包装类之间的自动转换。
自动类型转换适用于:
- 基本类型及其对应的包装类之间。比如,
int和Integer,float和Float,long和Long,boolean和Boolean等。 - 任意基本类型与任意包装类之间。如
int和long,byte和Integer等。 - 所有基本类型及包装类与
String之间。如boolean和String,Integer和String,float和String等。 - 枚举和
String之间。 - Java大数类型(
java.math.BigInteger,java.math.BigDecimal) 和Java基本类型(包括其包装类)与String之间。 - 其它情况详见MapStruct官方文档。
数字格式转换
在进行日期转换的时候,可以通过dateFormat标志指定日期的格式。
除此之外,对于数字的转换,也可以使用numberFormat指定显示格式:
枚举映射
为了在这些枚举项之间建立桥梁,可以使用@ValueMappings注解,可以包含多个@ValueMapping注解。
- 这里,将
source设置为三个具体枚举项之一,并将target设置为CARD。
集合映射
List映射
1 | |
Set和Map映射
集合映射策略
目标集合实现类型
进阶操作
依赖注入
到目前为止,我们一直在通过getMapper()方法访问生成的映射器:
1 | |
但是,如果使用的是Spring,只需要简单修改映射器配置,就可以像常规依赖项一样注入映射器。
修改 DoctorMapper 以支持Spring框架:
1 | |
在@Mapper注解中添加(componentModel = "spring"):是为了告诉MapStruct,在生成映射器实现类时,希望它能支持通过Spring的依赖注入来创建。
- 这样,生成的
DoctorMapperImpl会带有@Component注解, - 就不需要在接口中添加
INSTANCE字段了。
1 | |
只要被标记为@Component,Spring就可以把它作为一个bean来处理,就可以在其它类(如控制器)中通过@Autowire注解来使用它:
1 | |
如果你不使用Spring,MapStruct也支持Java CDI:
1 | |
添加默认值
@Mapping 注解有两个很实用的标志就是常量 constant 和默认值 defaultValue 。
- 无论
source如何取值,都将始终使用常量值; - 如果
source取值为null,则会使用默认值。
添加表达式
添加自定义方法
创建自定义映射器
@BeforeMapping、@AfterMapping
为了进一步控制和定制化,可以定义 @BeforeMapping 和 @AfterMapping方法。
- 显然,这两个方法是在每次映射之前和之后执行的。
- 也就是说,在最终的实现代码中,会在两个对象真正映射之前和之后添加并执行这两个方法。

