在 MyBatis-Plus 中,执行数据更新时,如果某些字段的值为 null,它们默认不会参与 SQL 更新操作。这是一个非常普遍且容易让开发者困惑的问题。
你是否也遇到过类似的情况?明明在代码中调用了 setXxx(null),期望清空数据库中的某个字段,但执行后却发现该字段依然保留着旧值。
这并非 Bug,而是 MyBatis-Plus 有意为之的设计。本文将按照 原因分析 → 解决方案 → 使用建议 的脉络,为你梳理清楚这个问题。
一、为什么 MyBatis-Plus 默认不更新 null?
这是 MyBatis-Plus 的默认设计策略,目的在于提升安全性和符合常规业务逻辑。
默认情况下,实体类字段的更新策略被设置为:
@TableField(updateStrategy = FieldStrategy.NOT_NULL)
这意味着一个核心规则:
字段值为 null 时,将不会出现在生成的 UPDATE SQL 语句中。
这样设计主要基于两点考虑:
- 安全性:避免因误操作意外将字段置为
null。
- 业务合理性:更贴合“仅更新已赋值字段”的常见业务场景。
因此,当你执行如下代码时:
User user = new User();
user.setId(1L);
user.setEmail(null); // 设置为 null
userMapper.updateById(user);
实际生成的 SQL 语句类似于:
UPDATE user SET id = ? WHERE id = ?
可以看到,email 字段根本没有被包含在更新语句中,其值也就不会被修改。
二、更新 null 字段的四种正确方式
方式一:使用 @TableField(updateStrategy = FieldStrategy.IGNORED) (推荐)
在特定的实体类字段上,直接修改其更新策略。
@TableField(updateStrategy = FieldStrategy.IGNORED)
private String email;
此注解的含义是:
忽略字段的更新策略,无论其值是否为 null,都参与更新操作。
此时,再执行相同的更新代码:
user.setEmail(null);
userMapper.updateById(user);
生成的 SQL 就会如预期所示:
UPDATE user SET email = NULL WHERE id = ?
适用场景:
- 该字段在业务上本身就允许被清空。
- 例如:用户备注、头像链接、手机号、邮箱等可选字段。
方式二:在实体类级别或通过全局配置统一策略
你可以为整个实体类或整个项目配置更新策略,但需谨慎使用。
1. 实体类级别(通过 @TableName 注解,不常用)
@TableName(value = “user”, autoResultMap = true)
public class User {
// 字段定义
}
2. 全局配置(在 application.yml 或 application.properties 中)
mybatis-plus:
global-config:
db-config:
update-strategy: ignored # 设置为忽略策略
这种方式会将所有字段的默认更新策略改为 IGNORED。
慎用原因:
- 导致所有未显式声明策略的字段都可以被更新为
null。
- 在复杂的业务系统中可能引入意料之外的数据覆盖风险,建议仅在明确所有字段均可置空的简单场景下使用。对于此类配置文件的管理和最佳实践,可以参考 技术文档 板块的规范建议。
方式三:使用 UpdateWrapper(最灵活)
如果不想修改实体类的注解,UpdateWrapper 提供了更灵活、动态的更新方式。
UpdateWrapper<User> wrapper = new UpdateWrapper<>();
wrapper.eq(“id”, 1L)
.set(“email”, null); // 显式设置字段为 null
userMapper.update(null, wrapper);
通过 wrapper.set() 方法,你可以精准控制需要更新的字段和值,无论原实体类的策略如何。此时生成的 SQL 为:
UPDATE user SET email = NULL WHERE id = ?
适用场景:
- 后台管理系统中的字段清空操作。
- 需要基于复杂条件的批量更新。
- 追求对最终生成的 SQL 有绝对控制权的场景。
方式四:使用 LambdaUpdateWrapper(类型安全,推荐)
这是 UpdateWrapper 的 Lambda 表达式版本,也是实际项目中最推荐的方式之一。
LambdaUpdateWrapper<User> wrapper = Wrappers.lambdaUpdate();
wrapper.eq(User::getId, 1L)
.set(User::getEmail, null); // 使用方法引用,类型安全
userMapper.update(null, wrapper);
优势:
- 类型安全:使用实体类的
getter 方法引用,编译期就能检查字段名正确性。
- IDE 友好:支持代码自动补全和重构,极大提升开发效率和减少手误。这种方法在构建健壮的 后端 & 架构 时非常有用。
三、一个容易忽略的“坑”:逻辑删除
当你的实体中使用了逻辑删除功能时,需要额外注意。
例如,有一个逻辑删除字段:
@TableLogic
private Integer deleted;
在进行更新操作(尤其是使用 Wrapper 方式)时,一定要确认:
- 你的
Wrapper 条件是否无意中被 MyBatis-Plus 自动添加的逻辑删除条件所影响(默认会加上 AND deleted = 0)。
- 有时更新失败,并不是因为
null 值问题,而是 目标记录已被逻辑删除,导致 WHERE 条件不匹配,从而更新了 0 条记录。
熟练掌握 MyBatis-Plus 的 null 值更新策略,是进行高效、精准 数据库 操作的基础。希望本文梳理的四种方法能帮助你彻底解决这一问题。如果你想深入探讨更多 Java 持久层框架的使用技巧,欢迎到 云栈社区 与更多开发者交流学习。