加了@TableLogic仍是物理删除,因MyBatis-Plus仅对BaseMapper标准方法(如deleteById)自动注入逻辑删除,手写@Update、XML SQL或Wrapper条件查询会绕过该机制;需确保字段类型匹配、正确配置value/delval,并优先用全局配置或显式添加删除条件。

为什么加了 @TableLogic 还是物理删除?
根本原因是 MyBatis-Plus 不会自动拦截所有 SQL——它只对 Mapper 接口里继承自 BaseMapper 的标准方法(如 deleteById、update)生效。手写 @Select、@Update 或 XML 里的 SQL 完全绕过逻辑删除机制。
常见错误现象:
- 调用
mapper.deleteById(1L)没问题,但执行mapper.delete(new QueryWrapper().lambda().eq(User::getId, 1L))却删了数据(因为 wrapper 构造的是 WHERE 条件,没触发字段自动补全) - XML 中写了
DELETE FROM user WHERE id = #{id},直接物理删 - 用了
lambdaQuery().remove()却没生效——这个方法底层仍走 XML 或自定义 SQL,不走逻辑删除流程
实操建议:
- 确保实体类字段上加了
@TableLogic,且该字段类型为布尔或整数(如Integer表示 0/1) - 全局配置优先于注解:如果
mybatis-plus.global-config.db-config.logic-delete-field已指定字段名,实体类上的@TableLogic会被忽略 - 测试时别只看返回值,要查数据库记录是否真被 SET 而非 DELETE
@TableLogic 的 value 和 delval 怎么配才不翻车?
这两个参数控制“什么值算已删除”,不是“删的时候设成啥”。很多人误以为 value = "1" 是删时写 1,其实是:当字段值等于 value 时,这条记录**被当成已删除**,查询时自动过滤;删操作则把字段设为 delval。
典型配置组合:
- 布尔型字段:
@TableLogic(value = "0", delval = "1")→ 未删为 false(0),删后为 true(1) - 整数型字段:
@TableLogic(value = "1", delval = "0")→ 未删为 1,删后为 0(有些老系统习惯用 0 表示无效) - 字符串字段(不推荐):
@TableLogic(value = "'N'", delval = "'Y'"),注意单引号必须写进字符串里
容易踩的坑:
- MySQL 中
TINYINT(1)常被 ORM 当布尔用,但如果 Java 字段是Boolean,而数据库存的是 0/1,MyBatis-Plus 默认能映射;但若字段是Integer,又配了value = "true",就会匹配失败,查不到数据 - delval 值如果和数据库默认值一样(比如字段 DEFAULT 0,delval 也设 0),那第一次删完再查就彻底看不见了——因为条件变成
WHERE deleted != 0,而新记录 deleted 是 NULL 或 0,全被过滤
全局逻辑删除配置比注解更靠谱吗?
更稳定,但灵活性差。全局配置在 application.yml 里设一次,所有实体统一走同一套规则,适合中后台系统;而 @TableLogic 支持不同表用不同字段、不同取值,适合混合旧库场景。
关键配置项(Spring Boot):
mybatis-plus:
global-config:
db-config:
logic-delete-field: deleted # 字段名,不用加@TableLogic
logic-delete-value: 1 # 已删除状态值
logic-not-delete-value: 0 # 未删除状态值注意点:
- 一旦启用全局配置,所有实体只要字段名匹配,就自动启用逻辑删除,
@TableLogic注解会被跳过 - 如果某张表逻辑删除字段叫
is_deleted,而其他表叫deleted,那就不能只靠全局配置,得回到注解 + 多个配置项方式 - MyBatis-Plus 3.4.3.4+ 开始支持
logic-delete-policy,可选严格模式(字段不存在时报错)或宽松模式(静默忽略),上线前建议开严格模式验一遍
逻辑删除后 count、分页、关联查询还准不准?
count 和分页基本准,前提是没手动写 SQL;但关联查询很容易漏掉逻辑删除条件,导致“查出已删的主表数据”或“连出已删的从表数据”。
真实场景举例:
-
select * from order o left join user u on o.user_id = u.id→ 如果user表启用了逻辑删除,这条 SQL 不会自动给u加AND u.deleted = 0,结果可能包含已删用户 -
lambdaQuery().eq(Order::getUserId, 123).count()→ 这个 count 是对order表做的,不受user表逻辑删除影响,但如果你本意是“有效用户下的有效订单数”,那就得手动加条件
应对办法:
- 关联查询一律用
QueryWrapper构建,显式写出.eq("u.deleted", 0),别依赖自动注入 - 复杂报表类 SQL 放 XML,但记得在 WHERE 里手动补逻辑删除条件,别图省事复制旧 SQL
- 如果用
MPJ(MyBatis-Plus-Join)或类似扩展,确认它是否支持逻辑删除字段透传——多数不支持,得自己 wrap 条件
最常被忽略的一点:软删除字段本身要不要加索引?答案是——要。尤其是高并发删/查场景,WHERE deleted = 0 如果没索引,分页性能会断崖下跌。

















