Mybatis-Plus更新操作时的一个坑

在 MyBatis-Plus(MP)开启逻辑删除 的情况下,updateById更新逻辑删除字段时, "看起来执行了但实际上没有更新"的问题是一种较为常见但不易察觉的问题。

背景:

项目中使用mybatis-plus且已开启逻辑删除:

复制代码
1 mybatis-plus:
2   global-config:
3     db-config:
4       logic-delete-field: deleted
5       logic-delete-value: 1
6       logic-not-delete-value: 0
7       update-strategy: not_empty   #更新策略只更新非空

在代码中获取记录并更新逻辑删除字段:

复制代码
1 useRecord record = RecordMapper.selectOne(...); 
2 record.setDeleted(1);
3 RecordMapper.updateById(record);

执行结果没有明显报错,也不会有异常日志,但实际没有更新逻辑删除字段。

如果没有记录执行返回值进行判断,将导致逻辑删除失败问题被隐藏,为后续业务埋雷。

原因分析:

mybatis-plus使用逻辑删除背景下,使用updateById时,如下代码段:

复制代码
1 record.setDeleted(1);
2 RecordMapper.updateById(record);

执行的sql语句是:

复制代码
1 UPDATE table_name 
2 SET deleted = 1 
3 WHERE id = ? 
4   AND deleted = 0;  -- ⚠️ MyBatis-Plus 自动添加的条件

这条 SQL 在语法层面是合法的,但在 MyBatis-Plus 的设计语义中,逻辑删除并不被视为一次普通的 update 操作。

MyBatis-Plus 将"删除"与"更新"在内部逻辑上进行了区分:updateById 被设计为只能作用于"未被逻辑删除的数据",而逻辑删除本身应通过 delete 系列方法触发。

复制代码
1 -- 你期望生成的SQL(框架不会生成)
2 UPDATE record SET deleted = 1 WHERE id = ? AND deleted = 0;
3 
4 -- updateById实际生成的SQL(deleted字段被剔除)
5 UPDATE record SET other_field=? WHERE id = ? AND deleted = 0;

Mybatis-Plus为什么要这样设计:

MyBtis-Plus 遵循 "语义隔离" 原则

操作类型 框架方法 SQL语义 设计意图
业务更新 updateById() 修改业务字段 只改数据内容,不改数据状态
逻辑删除 deleteById() UPDATE ... SET deleted=1 标记数据为"已删除"状态

关键机制:

  1. 拦截器过滤:LogicDeleteInterceptor 会自动移除 SET 子句中的逻辑删除字段
  2. 条件追加:所有查询/更新操作都会自动追加 AND deleted = 0
  3. 单向操作:逻辑删除被视为不可逆操作(删除后不应通过业务代码恢复)

如何正确处理:

  • 使用 MyBatis-Plus 提供的 deleteById 进行逻辑删除
复制代码
1 // 框架会自动生成:UPDATE user SET deleted=1 WHERE id=? AND deleted=0
2 recordMapper.deleteById(1L); 

在开启逻辑删除的前提下,deleteById 不会执行物理删除,而是由 MyBatis-Plus 自动生成逻辑删除 SQL,其语义与框架设计完全一致。

  • 用 LambdaUpdateWrapper 显式执行逻辑删除
复制代码
1  // 手动指定SET子句,绕过字段过滤
2 LambdaUpdateWrapper<Record> wrapper = new LambdaUpdateWrapper<>();
3 wrapper.eq(Record::getId, 1L).set(Record::getDeleted, 1); // 强制设置deleted字段
4 recordMapper.update(null, wrapper);

如果业务上必须通过 update 的方式完成逻辑删除,可以使用 LambdaUpdateWrapper,自行控制 WHERE 条件,避免与 MyBatis-Plus 的自动逻辑删除条件产生冲突。此方式绕过了 updateById 的内置逻辑删除约束,应明确其用途,避免被误用为普通更新。

  • 自定义 SQL 明确逻辑删除语
复制代码
1 @Update("UPDATE record SET deleted = 1 WHERE id = #{id} AND deleted = 0")
2 int logicDeleteById(Long id);

适合对逻辑删除行为有强语义要求的项目,便于代码阅读和后期维护。

总结:

在 MyBatis-Plus 中,逻辑删除不是一次普通的字段更新操作。updateById 被设计为只能更新"未被逻辑删除的数据",而不用于触发逻辑删除本身。当更新语句同时在 SET 和 WHERE 中涉及逻辑删除字段时,就会与框架的设计语义产生冲突。正确的做法是使用 delete 系列方法,或通过 Wrapper / 自定义 SQL 明确表达"逻辑删除"的意图。

相关推荐
郑重其事,鹏程万里15 分钟前
表达式计算器(mvel2)
java
其实防守也摸鱼17 分钟前
软件安全与漏洞--软件安全编码
java·前端·网络·安全·网络安全·web·工具
888CC++28 分钟前
栈上分配 VS 堆分配 核心区别
java·开发语言·jvm
艾利克斯冰28 分钟前
Java面试题汇总
java
我是一颗柠檬1 小时前
【JavaSE全面教学】Java集合框架下Day13(2026年)
java·开发语言·intellij-idea
vx-程序开发1 小时前
基于机器学习的动漫可视化系统的设计与实现-计算机毕业设计源码08339
java·c++·spring boot·python·spring·django·php
计算机魔术师1 小时前
【AI面试八股文 Vol.3.4:训练微调部署选型】从预训练到量化部署:LLM 工程落地如何做模型选择
人工智能·后端·面试·架构·moe·vol.3.3·vol.3.4
明月_清风1 小时前
从零到一构建生产级 AI Agent:架构拆解 × Python 高并发实战 × 技术选型方法论
后端·agent
LCG元2 小时前
RAG工程指南:从基础检索到生产部署全解析
java·运维·数据库
石榴树下的七彩鱼2 小时前
医疗票据 OCR 识别 API 多场景落地指南:医保结算 + 商保理赔 + 医疗信息化(附 Python/Java 完整示例)
java·python·ocr·石榴智能·医疗票据ocr·医保结算·ocrapi