条件构造器
MyBatis-Plus 提供了一套强大的条件构造器(Wrapper),用于构建复杂的数据库查询条件。
Wrapper 类允许开发者以链式调用的方式构造查询条件,无需编写繁琐的 SQL 语句,从而提高开发效率并减少 SQL 注入的风险。
主要的 Wrapper 类及其功能
| 类名 | 用途 | 字段引用方式 | 核心特点 |
|---|---|---|---|
| QueryWrapper | 查询 (SELECT) |
字符串 ("name") |
灵活,但字段名写错编译不报错 |
| UpdateWrapper | 更新 (UPDATE) |
字符串 ("name") |
专门用于构造更新条件和 SET 值 |
| LambdaQueryWrapper | 查询 (SELECT) |
Lambda (User::getName) |
类型安全,重构友好,防拼写错误 |
| LambdaUpdateWrapper | 更新 (UPDATE) |
Lambda (User::getName) |
类型安全,更新操作更安全 |
QueryWrapper是最基础的查询构造器
1
2
3
4
5
6
7
8// 需求:查询名字包含 "张",年龄大于 20 岁,且按年龄降序排列的用户
QueryWrapper<User> queryWrapper = new QueryWrapper<>();
queryWrapper.like("name", "张") // name LIKE '%张%'
.gt("age", 20) // AND age > 20
.orderByDesc("age"); // ORDER BY age DESC
// 执行查询
List<User> users = userMapper.selectList(queryWrapper);UpdateWrapper专门用于构建
UPDATE语句。除了构建WHERE条件外,它还有set方法,用于指定要更新的字段和值1
2
3
4
5
6
7// 需求:将所有名字包含 "张" 的用户,邮箱修改为 "test@mp.com"
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
updateWrapper.like("name", "张") // WHERE name LIKE '%张%'
.set("email", "test@mp.com"); // SET email = 'test@mp.com'
// 执行更新
userMapper.update(null, updateWrapper);如果不使用
set方法,MP 默认会根据你传入的实体对象中非空字段进行更新。LambdaQueryWrapper,利用 Java 8 的 Lambda 表达式,通过
User::getName这种“方法引用”来代替"name"字符串。1
2
3
4
5
6// 需求:查询年龄为 20 且邮箱不为空的用户
LambdaQueryWrapper<User> lambdaQueryWrapper = new LambdaQueryWrapper<>();
lambdaQueryWrapper.eq(User::getAge, 20) // age = 20
.isNotNull(User::getEmail); // email IS NOT NULL
List<User> users = userMapper.selectList(lambdaQueryWrapper);LambdaUpdateWrapper同理,在设置更新字段时,也可以使用 Lambda 表达式,避免硬编码。
1
2
3
4
5
6// 需求:将 ID 为 1 的用户年龄改为 25
LambdaUpdateWrapper<User> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
lambdaUpdateWrapper.eq(User::getId, 1) // WHERE id = 1
.set(User::getAge, 25); // SET age = 25
userMapper.update(null, lambdaUpdateWrapper);除了上面提到的 Wrapper,MP 还提供了
LambdaQueryChainWrapper,这种写法不需要 new Wrapper,直接链式调用1
2
3
4
5// 这种写法不需要 new Wrapper,直接链式调用
List<User> list = new LambdaQueryChainWrapper<>(userMapper)
.eq(User::getAge, 20)
.like(User::getName, "张")
.list(); // 最后调用 list() 或 one() 执行
而对于AbstractWrapper,这是一个抽象基类,提供了所有
Wrapper
类共有的方法和属性。它定义了条件构造的基本逻辑,包括字段(column)、值(value)、操作符(condition)等。所有的
QueryWrapper、UpdateWrapper、LambdaQueryWrapper
和 LambdaUpdateWrapper 都继承自
AbstractWrapper。
而且 MyBatis-Plus 提供了 Wrappers
类,它是一个静态工厂类,用于快速创建
QueryWrapper、UpdateWrapper、LambdaQueryWrapper
和 LambdaUpdateWrapper 的实例。使用 Wrappers
可以减少代码量,提高开发效率。
1 | // 创建 QueryWrapper |
Kotlin支持
QueryWrapper和UpdateWrapper,但不支持LambdaQueryWrapper和LambdaUpdateWrapper。如果需要使用Lambda风格的Wrapper,可以使用KtQueryWrapper和KtUpdateWrapper。因为Lambda式链式调用不支持Kotlin。
常用方法
基础比较运算
这是最常用的部分,用于构建 WHERE column = value
类型的条件。
| 方法 | 说明 | SQL 示例 | 备注 |
|---|---|---|---|
| eq | 等于 (=) | age = 18 |
最常用的方法 |
| ne | 不等于 (<>) | age <> 18 |
|
| gt | 大于 (>) | age > 18 |
Greater Than |
| ge | 大于等于 (>=) | age >= 18 |
Greater or Equal |
| lt | 小于 (<) | age < 18 |
Less Than |
| le | 小于等于 (<=) | age <= 18 |
Less or Equal |
| allEq | 批量等于 | id=1 AND name='Tom' |
传入 Map,批量添加 eq 条件 |
分组、排序与字段选择
用于构建 GROUP BY、ORDER BY
和指定查询字段。
| 方法 | 说明 | SQL 示例 |
|---|---|---|
| groupBy | 分组 | GROUP BY id, name |
| having | Having 子句 | HAVING sum(age) > 10 |
| orderByAsc | 升序排列 | ORDER BY id ASC |
| orderByDesc | 降序排列 | ORDER BY id DESC |
| orderBy | 动态排序 | ORDER BY id ASC, age DESC |
| select | 指定查询字段 | SELECT id, name |
更新专用
这些方法仅在 UpdateWrapper 或
LambdaUpdateWrapper 中使用,用于指定 SET
部分。
| 方法 | 说明 | SQL 示例 |
|---|---|---|
| set | 设置字段值 | SET name = '新名字' |
| setSql | 设置 SQL 片段 | SET age = age + 1 |
| setIncrBy | 字段自增 | SET age = age + 1 |
| setDecrBy | 字段自减 | SET age = age - 1 |
模糊查询与范围
用于处理字符串匹配、区间查询和空值判断。
| 方法 | 说明 | SQL 示例 | 备注 |
|---|---|---|---|
| like | 模糊查询 | name LIKE '%张%' |
两边都有 % |
| notLike | 非模糊查询 | name NOT LIKE '%张%' |
|
| likeLeft | 左模糊 | name LIKE '%张' |
左边有 % |
| likeRight | 右模糊 | name LIKE '张%' |
右边有 % (常用于搜索框) |
| notLikeLeft | 非左模糊 | name NOT LIKE '%张' |
|
| notLikeRight | 非右模糊 | name NOT LIKE '张%' |
|
| between | 范围查询 | age BETWEEN 18 AND 20 |
包含边界值 |
| notBetween | 非范围查询 | age NOT BETWEEN 18 AND 20 |
|
| isNull | 字段为空 | email IS NULL |
|
| isNotNull | 字段不为空 | email IS NOT NULL |
(你列表中未列出,但也常用) |
| in | IN 查询 | id IN (1, 2, 3) |
传入集合或数组 |
| notIn | NOT IN 查询 | id NOT IN (1, 2, 3) |
SQL 拼接
这些方法允许你直接编写 SQL 片段,MP 会自动处理参数预编译,防止 SQL 注入。
| 方法 | 说明 | SQL 示例 | 使用场景 |
|---|---|---|---|
| inSql | 字段 IN (子查询) | id IN (select id from user where id < 3) |
子查询场景 |
| notInSql | 字段 NOT IN (子查询) | id NOT IN (select id from user where id < 3) |
|
| eqSql | 等于 (SQL片段) | id = (select max(id) from user) |
右侧是 SQL 而非固定值 |
| gtSql/geSql | 大于/大于等于 (SQL片段) | age > (select avg(age) from user) |
|
| ltSql/leSql | 小于/小于等于 (SQL片段) | age < (select avg(age) from user) |
|
| apply | 拼接原生 SQL | date_format(create_time, '%Y-%m') = '2023-01' |
数据库函数处理,如日期格式化 |
| last | 直接拼接到末尾 | LIMIT 1 |
慎用,直接拼接字符串,有注入风险 |
逻辑组合与嵌套
用于处理复杂的 AND、OR
以及括号嵌套逻辑。
| 方法 | 说明 | SQL 示例 | 备注 |
|---|---|---|---|
| and | AND 嵌套 | AND (age > 10 AND name = 'a') |
传入 Lambda,自动加括号 |
| or | OR 连接 | age > 10 OR name = 'a' |
打断默认的 AND 链式调用 |
| nested | 普通嵌套 | (age > 10 OR name = 'a') |
仅加括号,不加 AND/OR 前缀 |
| func | 函数式封装 | - | 用于抽取公共的 Wrapper 构建逻辑 |
| exists | EXISTS 查询 | EXISTS (select id from ...) |
判断子查询是否有结果 |
| notExists | NOT EXISTS 查询 | NOT EXISTS (select id from ...) |
特殊方法
lambda:
- 这是一个转换方法。如果你在代码中已经 new 了一个
QueryWrapper,但中途想用 Lambda 写法,可以调用.lambda()方法将其转换为LambdaQueryWrapper。 - 例如:
queryWrapper.lambda().eq(User::getName, "Tom")。
1 | UpdateWrapper<User> updateWrapper = new UpdateWrapper<>(); |
lambda方法返回一个LambdaWrapper对象,具体类型取决于调用它的Wrapper类型。
线程安全性
Wrapper 实例不是线程安全的,因此建议在每次使用时创建新的 Wrapper 实例。这样可以避免多线程环境下的数据竞争和潜在的错误
1 | // 在每个方法或请求中创建新的 Wrapper 实例 |
流式查询
流式查询
流式查询(Streaming Query) 是一种逐条获取查询结果的数据库访问模式。与传统查询方式相比,它不会一次性将所有结果加载到内存中,而是通过游标(Cursor)逐条处理。
传统的 list()
查询会将所有数据一次性加载到内存中,容易导致
OutOfMemoryError
(OOM)。而流式查询则是“查一条、处理一条、扔一条”,内存中始终只保持少量的数据对象,从而极大地降低内存占用。
MyBatis-Plus 从 3.5.4 版本开始支持流式查询,这是 MyBatis
的原生功能,通过 ResultHandler
接口实现结果集的流式查询。这种查询方式适用于数据跑批或处理大数据的业务场景。
在 BaseMapper 中,新增了多个重载方法,包括
selectList, selectByMap,
selectBatchIds, selectMaps,
selectObjs,这些方法可以与流式查询结合使用。
基于 Cursor (游标)
的流式查询
这是最直观的流式查询方式,类似于 JDBC 的
ResultSet。它返回一个 Cursor
对象,你可以通过遍历这个游标来逐条获取数据。
MyBatis 提供了一个叫 org.apache.ibatis.cursor.Cursor
的接口类用于流式查询,这个接口继承了 java.io.Closeable 和
java.lang.Iterable 接口,由此可知:
- Cursor 是可关闭的;
- Cursor 是可遍历的。
除此之外,Cursor 还提供了三个方法:
- isOpen(): 用于在取数据之前判断 Cursor 对象是否是打开状态。只有当打开时 Cursor 才能取数据;
- isConsumed(): 用于判断查询结果是否全部取完。
- getCurrentIndex(): 返回已经获取了多少条数据
使用流式查询,则要保持对产生结果集的语句所引用的表的并发访问,因为其 查询会独占连接,所以必须尽快处理。
你需要自定义一个 Mapper 方法,返回类型必须是
Cursor<T>,并配合 @Options
注解配置游标属性。
1 | public interface UserMapper extends BaseMapper<User> { |
resultSetType = ResultSetType.FORWARD_ONLY:告诉数据库驱动,这个游标是只进的,不要缓存所有结果集。这是流式查询生效的关键。fetchSize = 1000:每次从数据库网络传输中获取 1000 条数据到本地缓存。如果设为Integer.MIN_VALUE,通常是每次只取 1 条(取决于驱动实现),设大一点可以减少网络交互次数。
在 Service 中使用, 使用 Cursor
必须在事务范围内,或者手动管理连接,否则游标会在方法结束时立即关闭,导致遍历报错。
1 |
|
基于 ResultHandler
的回调处理
这是 MyBatis 原生的流式处理方式,MP 3.5.4+ 在 BaseMapper
中直接封装了支持。这种方式不需要返回
Cursor,而是通过回调函数处理每一条数据。
直接调用 baseMapper.selectList 的重载方法,传入
ResultHandler。
1 |
|
- 这样的代码更紧凑,不需要自定义 Mapper XML
或注解。但是逻辑写在回调里,不如
Cursor的for-each循环直观。
流式查询的一些问题
事务超时问题:
- 因为流式查询需要长时间保持数据库连接来读取数据,如果你的数据处理逻辑很慢(比如处理几百万条数据耗时 1 小时),很容易触发事务超时。
- 解决:在
@Transactional注解中设置超时时间,例如@Transactional(timeout = 3600)。
连接池耗尽:
- 流式查询会长时间占用一个数据库连接。如果并发执行多个流式查询任务,可能会把连接池(如 HikariCP)的连接占满,导致其他正常业务无法获取连接。
- 解决:为流式查询配置独立的、较小的连接池,或者限制并发度。
必须在事务中:
- 如前所述,
Cursor依赖数据库连接保持打开。Spring 的事务管理器会在方法结束时关闭连接。所以必须在@Transactional方法内使用,或者使用TransactionTemplate手动控制。
不要跳过行:
- 流式查询是“流”,你不能像
List那样随机访问(比如list.get(500))。你必须按顺序遍历,如果你想处理第 500 条,必须先“流过”前 499 条。
MySQL 的特殊配置:
- 在 MySQL 中,要实现真正的流式查询(不缓存到内存),通常还需要在 JDBC
URL 连接字符串中设置
useCursorFetch=true,或者在 Mapper 注解中明确指定fetchSize。
代码生成器
AutoGenerator 是 MyBatis-Plus 的代码生成器,通过 AutoGenerator 可以快速生成 Entity、Mapper、Mapper XML、Service、Controller 等各个模块的代码,极大的提升了开发效率。
https://baomidou.com/guides/new-code-generator/
代码生成器最少只需要mybatis-plus-boot-starter、mybatis-plus-generator、freemarker这三个依赖,如果没有添加模板引擎依赖会报错
1 | <dependencies> |
创建一个 Java 类,在其中配置并执行代码生成。这是最核心的部分,主要分为全局配置、数据源配置、包配置和策略配置。常用配置的清单如下
1 | public class MybatisPlusGenerator { |
你可以向模板中注入自定义的属性,在模板文件中通过
${cfg.你的属性名} (Freemarker) 来获取。
1 | InjectionConfig injectionConfig = new InjectionConfig() { |
但是,我们很少使用配置类的代码生成器,使用 IDEA 的图形化插件,例如 MyBatisX 或 MyBatisPlus 插件。它们提供了可视化的界面来配置数据库连接和代码生成策略,通过简单的点击就能完成代码生成,非常适合快速开发。
MyBatis 高级内容
主键生成策略
MP 的主键生成策略是通过 @TableId 注解的
type 属性或全局配置来指定
如果前面提到 @TableId 注解的 type
属性不能满足数据库原生序列的要求,需要进行额外定义,那么主键生成策略必须使用
INPUT 类型,这意味着主键值需要由用户在插入数据时提供,MP
不自动生成主键。MyBatis-Plus
内置支持多种数据库的主键生成策略,包括:
| 生成器类 | 适配数据库 | 核心作用 |
|---|---|---|
DB2KeyGenerator |
DB2 | 适配 DB2 数据库的序列生成主键 |
H2KeyGenerator |
H2 | 适配 H2 数据库的序列 / 自增 |
KingbaseKeyGenerator |
人大金仓 | 适配国产金仓数据库的序列 |
OracleKeyGenerator |
Oracle | 适配 Oracle 数据库的序列 |
PostgreKeyGenerator |
PostgreSQL | 适配 PostgreSQL 的序列 |
下面是一个使用 @KeySequence
注解的实体类示例,就把我上面的例子改造一下
1 | import com.baomidou.mybatisplus.annotation.*; |
然后,需将 OracleKeyGenerator 注入 Spring 容器,MP
会自动识别并使用该生成器为 @KeySequence
注解的实体生成主键
1 | import com.baomidou.mybatisplus.extension.incrementer.OracleKeyGenerator; |
若项目中多个实体需要统一主键策略,可通过全局配置替代局部注解,减少重复代码
首先在application.yml 中补充全局主键配置
1 | mybatis-plus: |
- 配置后,所有实体的
@TableId若不指定type,默认使用ASSIGN_ID; - 若需覆盖全局策略,只需在实体类的
@TableId中指定type,如IdType.AUTO。
若内置生成器无法适配你的业务,如自定义雪花算法、业务规则生成主键,可实现
IKeyGenerator 接口来扩展
实现 IKeyGenerator 接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14import com.baomidou.mybatisplus.core.incrementer.IKeyGenerator;
import org.springframework.stereotype.Component;
import java.util.UUID;
// 自定义主键生成器(生成 "USER_" + UUID 的字符串主键)
public class CustomKeyGenerator implements IKeyGenerator {
public String executeSql(String incrementerName) {
// incrementerName 对应 @KeySequence 的 value 属性
return "USER_" + UUID.randomUUID().toString().replace("-", "");
}
}注册生成器
1
2
3
4
5
6
7
public class MyBatisPlusConfig {
public IKeyGenerator customKeyGenerator() {
return new CustomKeyGenerator();
}
}实体类使用
1
2
3
4
5
6
7
8
9// 自定义序列名
public class User {
// 必须用 INPUT 策略
private String id; // 主键类型改为 String
private String username;
private Integer age;
}
自定义ID生成器
首先看 MyBatis-Plus 自带主键生成策略对比,这是 IdentifierGenerator 接口中的两个方法
| 方法 | 主键生成策略 | 主键类型 | 说明 |
|---|---|---|---|
| nextId | ASSIGN_ID | Long,Integer,String | 支持自动转换为String类型,但数值类型不支持自动转换,需精准匹配,例如返回Long,实体主键就不支持定义为Integer |
| nextUUID | ASSIGN_UUID | String | 默认不含中划线的UUID生成 |
MyBatis-Plus 提供了多种方式来实现自定义ID生成器,以下是一些示例,还是基于上面改造的
声明为 Bean 供 Spring 扫描注入,也就是说,实现 IdentifierGenerator 接口
1 |
|
然后用配置类注册
1 |
|
然后怎么让 MyBatis-Plus 使用这个自定义生成器?
1 |
|
对于nextId,使用ASSIGN_ID 让 MP 调用
IdentifierGenerator,自定义的
CustomIdGenerator 会自动生效
实现 IKeyGenerator
接口和IdentifierGenerator接口来扩展看起来类似,但是二者有根本上的区别
| 接口 | 全称 | 作用 / 定位 | 适用场景 |
|---|---|---|---|
IKeyGenerator |
数据库键生成器 | 靠数据库生成主键(序列、自增) | Oracle、DB2、PostgreSQL 等依赖数据库序列的场景 |
IdentifierGenerator |
标识符生成器 | 靠 Java 代码生成主键(雪花算法、UUID) | MySQL、分布式系统,不依赖数据库,纯代码生成 |
- 对于
IKeyGenerator,生成主键的工作交给数据库,MP 只是去数据库拿值。 - 对于
IdentifierGenerator,在 Java 代码里直接生成 ID,MP 默认的 雪花算法、UUID 都是它实现的。
逻辑删除支持
逻辑删除是一种优雅的数据管理策略,它通过在数据库中标记记录为“已删除”而非物理删除,来保留数据的历史痕迹,同时确保查询结果的整洁性。MyBatis-Plus 提供了便捷的逻辑删除支持,使得这一策略的实施变得简单高效。
MyBatis-Plus 的逻辑删除功能会在执行数据库操作时自动处理逻辑删除字段。以下是它的工作方式:
- 插入:逻辑删除字段的值不受限制。
- 查找:自动添加条件,过滤掉标记为已删除的记录。
- 更新:防止更新已删除的记录。
- 删除:将删除操作转换为更新操作,标记记录为已删除。
例如:
- 删除:
update user set deleted=1 where id = 1 and deleted=0 - 查找:
select id,name,deleted from user where deleted=0
对于字段类型支持,虽然不仅仅是 0 和 1,但是一般情况下,还是用0和1
如果是需要全局配置,在 application.yml
中统一配置,适用于全项目统一的逻辑删除规范。
1 | mybatis-plus: |
- 配置一次,所有实体类只要包含名为
deleted的字段,都会自动生效,无需重复加注解。
局部配置就是在实体类的字段上使用 @TableLogic 注解
1 | import com.baomidou.mybatisplus.annotation.TableLogic; |
配置好后,MP 会在底层自动拦截并修改 SQL 语句,这对业务代码是透明的。
(确保使用的是 MP 提供的 deleteById 方法。如果是自定义
SQL,MP 的逻辑删除拦截器可能不会生效,需要手动处理。)
| 操作类型 | 你的代码调用 | MP 生成的实际 SQL | 说明 |
|---|---|---|---|
| 删除 | userMapper.deleteById(1L) |
UPDATE user SET deleted=1 WHERE id=1 AND deleted=0 |
DELETE 被转换为 UPDATE,并追加
deleted=0 条件防止重复删除。 |
| 查询 | userMapper.selectList(...) |
SELECT * FROM user WHERE ... AND deleted=0 |
所有查询自动追加
AND deleted=0,确保查不到已删除数据。 |
| 更新 | userMapper.updateById(user) |
UPDATE user SET ... WHERE id=1 AND deleted=0 |
更新时也保护性地加上条件,防止更新已删除数据。 |
逻辑删除字段通常在插入时应该是“未删除”状态。所以说,一般情况下,在建表时设置
deleted 字段默认值为
0是很好的实践。当然你也可以通过自动填充类似审计的功能,使用
@TableField(fill = FieldFill.INSERT) 配合
MetaObjectHandler 自动填充 0。
但是逻辑删除 + 唯一索引冲突的情况怎么办
假设 username 字段有唯一索引。用户 A 删除了账号
zhangsan(逻辑删除,deleted=1)。此时如果你想重新注册一个
zhangsan,数据库会报错“唯一键冲突”,因为数据库里还存着那条
deleted=1 的记录。
这时候,使用 0 和 1 就显得很局限了,我们试着将将逻辑删除字段设为
datetime 类型。未删除值为 'null',已删除值为
'now()'。然后将唯一索引改为联合唯一索引
UNIQUE KEY idx_username_del (username, deleted)。
- 正常数据:
deleted为NULL,username必须唯一。 - 删除数据:
deleted变为具体时间(如2023-10-01 12:00:00)。 - 新注册:因为
deleted是NULL,它与库中已删除的(deleted不为NULL)记录不冲突,完美解决!
批量操作
批量操作是一种高效处理大量数据的技术,它允许开发者一次性执行多个数据库操作,从而减少与数据库的交互次数,提高数据处理的效率和性能。在MyBatis-Plus中,批量操作主要用于以下几个方面:
数据插入(Insert):批量插入是批量操作中最常见的应用场景之一。通过一次性插入多条记录,可以显著减少SQL语句的执行次数,加快数据写入速度。这在数据迁移、初始化数据等场景中尤为有用。
使用
saveBatch时,如果数据库是 MySQL 且开启了rewriteBatchedStatements=true,可能无法获取自动生成的主键 ID。数据更新(Update):批量更新允许同时修改多条记录的特定字段,适用于需要对大量数据进行统一变更的情况,如批量修改用户状态、更新产品价格等。
数据删除(Delete):批量删除操作可以快速移除数据库中的多条记录,常用于数据清理、用户注销等场景。
那么,批量操作很明显和流式查询是不太一样的,不要混为一谈
- 流式查询是为了解决读取大量数据时的内存溢出问题(读)。
- 批量操作是为了解决写入/修改大量数据时的性能瓶颈(写)。
MP 的批量操作底层依赖的是 JDBC 的 addBatch() /
executeBatch() 机制。
开启事务时:
- MP 会获取
SqlSession。- 将 SQL 加入 Batch(
addBatch),但不立即发送给数据库。- 当达到批次大小(默认 1000 条)或循环结束时,统一执行
executeBatch()。- 最后提交事务。
所以说不管是流失查询还是批量操作,都建议配合
@Transactional注解使用!
MP 的 IService
接口提供了两个最常用的批量方法:saveBatch 和
updateBatchById。
批量插入:
saveBatch用于一次性插入大量数据。
1
2
3
4
5
6
7
8// 准备 1000 个用户
List<User> userList = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
userList.add(new User("User" + i, i));
}
// 执行批量插入
boolean success = userService.saveBatch(userList);批量更新:
updateBatchById用于根据 ID 批量更新数据。
1
2
3
4
5
6
7// 准备更新数据
List<User> userList = new ArrayList<>();
userList.add(new User(1L, "NewName1", 20));
userList.add(new User(2L, "NewName2", 21));
// 执行批量更新
boolean success = userService.updateBatchById(userList);批量删除:MP 没有专门的
removeBatchByIds方法在 Service 层直接暴露。用Mapper层中的deleteBatchIds1
2// 批量删除 ID 为 1, 2, 3 的用户
userMapper.deleteBatchIds(Arrays.asList(1, 2, 3));
默认情况下,即使使用了
saveBatch,MySQL 驱动发送的 SQL 可能是这样的:
1
2
3
4 INSERT INTO user (name, age) VALUES (?, ?);
INSERT INTO user (name, age) VALUES (?, ?);
INSERT INTO user (name, age) VALUES (?, ?);
...虽然是一次性发送,但数据库还是把它看作多条独立的插入语句,解析开销依然存在。
在 JDBC 连接 URL 中添加
rewriteBatchedStatements=true参数:
1 jdbc:mysql://localhost:3306/mydb?rewriteBatchedStatements=true开启后,MySQL 驱动会将多条 INSERT 语句合并为一条真正的多值插入:
1 INSERT INTO user (name, age) VALUES (?, ?), (?, ?), (?, ?);
字段脱敏
字段脱敏与字段加密不同,字段脱敏通常是指数据在数据库中是明文,但在查询出来返回给前端时,自动将中间几位替换为星号
使用 mybatis-mate,然后在实体类的敏感字段上添加
@FieldSensitive 注解。MP 内置了 9 种常用的脱敏策略。
1 | import com.baomidou.mybatisplus.extension.handlers.FieldSensitive; |
如果内置策略不满足需求,你可以定义自己的策略并注册到 Spring 容器中:
1 |
|
这样,查询后自动处理,返回给前端就是脱敏后的数据。如果在编辑场景需要明文,可以调用
RequestDataTransfer.skipSensitive() 来临时跳过。
使用 Jackson 注解指定序列器这种方式比较常规和通用,我就不再强调了
多数据源支持
随着项目规模的扩大,单一数据源已无法满足复杂业务需求,多数据源(动态数据源)应运而生。本文将介绍两种
MyBatis-Plus 的多数据源扩展插件:开源生态的
dynamic-datasource 和 企业级生态的
mybatis-mate。
这里只说dynamic-datasource,使用它非常简单
dynamic-datasource 是一个开源的 Spring Boot
多数据源启动器,提供了丰富的功能,包括数据源分组、敏感信息加密、独立初始化表结构等
1 | <dependency> |
配置数据源
1 | spring: |
使用 @DS 切换数据源:
1 |
|





