源本科技 | 码上会

逻辑删除支持

2026/04/03
2
0

引言

逻辑删除是一种优雅且安全的数据管理策略,它不会真正从数据库中删除记录,而是通过专用字段标记记录为已删除状态,既能保留数据历史痕迹、满足审计与数据恢复需求,又能保证正常业务查询只返回有效数据,避免物理删除导致的数据永久丢失风险。

MyBatis-Plus 对逻辑删除提供了零侵入、自动化的原生支持,无需手动编写 SQL 条件,框架会在底层自动完成逻辑删除的处理。


工作原理

MyBatis-Plus 会在执行 CRUD 操作时,自动对逻辑删除字段进行拦截处理,规则如下:

操作类型

框架自动处理逻辑

示例 SQL

插入

逻辑删除字段赋值为未删除状态

insert into user (..., deleted) values (..., 0)

查询

自动拼接 WHERE 逻辑字段 = 未删除值,过滤已删除数据

select ... from user where deleted = 0

更新

自动拼接条件,禁止更新已删除数据

update user set ... where id = ? and deleted = 0

删除

将物理删除转换为更新操作,标记为已删除

update user set deleted = 1 where id = ? and deleted = 0

核心特点:对业务代码完全透明,开发者无需修改原有逻辑,即可实现逻辑删除。


推荐方案

逻辑删除支持所有数据库字段类型,推荐根据业务场景选择,不同类型配置方式不同:

最常用类型(推荐)

  • Integer(int)

    • 未删除值:0

    • 已删除值:1

    • 优点:占用空间小、查询效率高、适配绝大多数业务场景

  • Boolean

    • 未删除值:false

    • 已删除值:true

    • 优点:语义清晰,代码可读性高

时间戳类型(高级场景)

  • LocalDateTime / datetime

    • 未删除值:null

    • 已删除值:now()(当前时间)

    • 用途:可记录删除时间,满足审计需求

  • BigInt

    • 未删除值:0

    • 已删除值:UNIX_TIMESTAMP()(时间戳)

    • 用途:可作为唯一索引组合字段,支持同一数据多次逻辑删除


使用方法

使用前准备:数据库表必须添加逻辑删除字段(如 deleted

全局配置(推荐)

全局配置(推荐,统一管理)

application.yml 中配置全局逻辑删除规则,所有实体类通用,无需单独注解。

mybatis-plus:
  global-config:
    db-config:
      logic-delete-field: deleted  # 全局逻辑删除字段名(实体类属性名)
      logic-delete-value: 1        # 逻辑已删除值
      logic-not-delete-value: 0    # 逻辑未删除值

注解局部配置

注解局部配置(灵活定制)

不使用全局配置,在单个实体类字段上添加 @TableLogic 注解,单独配置规则。

import com.baomidou.mybatisplus.annotation.TableLogic;

public class User {

    private Long id;
    private String username;

    // 局部逻辑删除注解
    @TableLogic(value = "0", delval = "1")
    private Integer deleted;
}
  • value:逻辑未删除值(默认 0)

  • delval:逻辑已删除值(默认 1)

优先级:局部注解 > 全局配置,单个实体可覆盖全局规则。


实体类与字段

标准实体类

import com.baomidou.mybatisplus.annotation.TableLogic;
import lombok.Data;

@Data
public class User {
    private Long id;
    private String username;
    
    // 全局配置下,只需添加注解即可(无需指定值)
    @TableLogic
    private Integer deleted;
}

插入时自动赋值

未删除状态

推荐 3 种方案,保证新数据默认是未删除状态:

  1. 数据库默认值(最简单):给 deleted 字段设置默认值 0

  2. 实体类默认值private Integer deleted = 0;

  3. 自动填充功能:通过 MP 自动填充统一赋值(适合微服务统一规范)


常用操作示例

自动生效

删除操作

自动转为逻辑删除

// 调用原生删除方法,自动执行 update 标记删除
userMapper.deleteById(1L);
userService.removeById(1L);

查询操作

自动过滤已删除数据

// 自动拼接 where deleted = 0,只查询有效数据
userMapper.selectById(1L);
userService.list();

查询已删除数据

特殊场景

如果需要查询已删除的记录,必须使用自定义 SQL,MP 不会拦截手动编写的 SQL:

@Select("select * from user where deleted = 1")
List<User> selectDeletedUsers();

IdentifierGenerator

ID 生成器 + 逻辑删除

逻辑删除功能 与自定义 ID 生成器完全兼容,互不影响:

  • 插入时:ID 生成器生成主键 → 逻辑删除字段赋值为未删除

  • 删除时:仅更新逻辑删除字段,不修改主键

可在同一个项目中同时使用,无任何冲突。


逻辑删除与物理删除

维度

物理删除

逻辑删除

执行方式

delete from table

update table set deleted=1

数据状态

永久删除,不可恢复

保留记录,仅标记状态

查询结果

不显示

默认不显示,可手动查询

适用场景

测试数据、临时数据、无需留存数据

业务数据、订单、用户、审计数据

优点

节省空间、性能高

数据可恢复、支持审计、安全

缺点

数据丢失风险高

表数据量大时需定期清理


常见问题

逻辑删除不生效

  • 未添加 @TableLogic 注解

  • 全局配置字段名与实体类属性名不一致

  • 使用了自定义 SQL(MP 仅拦截内置方法)

如何恢复已删除数据?

直接执行更新操作,将逻辑删除字段改为未删除值即可:

UpdateWrapper<User> wrapper = new UpdateWrapper<>();
wrapper.eq("id", 1L).set("deleted", 0);
userMapper.update(null, wrapper);

联合唯一索引

如果逻辑删除字段参与唯一索引,推荐使用 时间戳类型,避免重复数据无法插入。

不适合逻辑删除的场景

  • 数据无需留存,如日志、临时缓存

  • 超高并发写入,追求极致性能

  • 业务上永久不需要查询历史数据


总结

  1. 逻辑删除是标记删除而非物理删除,MP 自动拦截 CRUD 实现无侵入使用

  2. 推荐使用 Integer 类型 + 全局配置,简单高效、适配绝大多数场景

  3. 配置优先级:局部 @TableLogic 注解 > 全局配置

  4. 与自定义 ID 生成器完全兼容,可同时使用

  5. 内置方法自动生效,自定义 SQL 需手动处理逻辑删除条件