MyBatis-Plus逻辑删除实战指南:从配置到最佳实践(附完整案例)
目录导读
- 逻辑删除核心概念 – 为什么需要逻辑删除?物理删除的痛点分析
- MyBatis-Plus全局配置 – 一步步搭建逻辑删除环境(YAML/XML两种方式)
- 实体类注解详解 –
@TableLogic的巧妙用法与字段设计规范 - CRUD自动改写机制 – 插入、查询、删除、更新如何被自动拦截
- 完整电商订单案例 – 用户删除订单、管理员查询已删除数据、恢复数据
- 常见坑与性能优化 – 唯一索引冲突、联表查询过滤、逻辑删除+分页
- 高频面试问答 – 逻辑删除 vs 物理删除、如何实现回收站功能
逻辑删除核心概念:业务数据的“软着陆”
在真实业务场景中,物理删除(DELETE) 会导致数据彻底消失,无法追溯审计、无法恢复误删数据,而逻辑删除通过增加一个标记字段(如deleted),将删除操作转换为UPDATE语句,让数据“隐身”而非“消失”。

痛点对比:
| 场景 | 物理删除 | 逻辑删除 |
|------|----------|----------|
| 订单误删恢复 | ❌ 永久丢失 | ✅ 修改标记位即可 |
| 数据库容量 | 较小 | 需定期清理垃圾数据 |
| 查询性能 | 直接忽略 | 需追加WHERE deleted=0 |
MyBatis-Plus从3.1.1版本起,将逻辑删除功能内置化,通过零侵入的配置即可实现全局自动改写。
MyBatis-Plus全局配置:两分钟快速搭建
1 Spring Boot YAML配置方式
mybatis-plus:
global-config:
db-config:
logic-delete-field: deleted # 全局逻辑删除字段名(默认deleted)
logic-delete-value: 1 # 已删除标记值(默认1)
logic-not-delete-value: 0 # 未删除标记值(默认0)
2 XML/Properties配置方式(旧版本)
<configuration>
<globalConfig>
<dbConfig>
<logicDeleteField>deleted</logicDeleteField>
<logicDeleteValue>1</logicDeleteValue>
<logicNotDeleteValue>0</logicNotDeleteValue>
</dbConfig>
</globalConfig>
</configuration>
3 数据库表设计建议
CREATE TABLE `orders` ( `id` BIGINT PRIMARY KEY AUTO_INCREMENT, `order_no` VARCHAR(32) NOT NULL, `user_id` BIGINT NOT NULL, `deleted` TINYINT(1) DEFAULT 0 COMMENT '逻辑删除:0未删除,1已删除', `create_time` DATETIME, `update_time` DATETIME, INDEX idx_user_deleted (`user_id`, `deleted`) ) ENGINE=InnoDB;
实体类注解:@TableLogic的三种用法
1 字段级别注解(推荐)
@Data
@TableName("orders")
public class Order {
@TableId(type = IdType.AUTO)
private Long id;
@TableLogic // 关键注解
private Integer deleted;
}
2 全局配置省略注解
如果YAML中已配置logic-delete-field: deleted,且实体类字段名与配置一致,可省略@TableLogic,MP自动识别。
3 自定义标记值
@TableLogic(value = "0", delval = "2") // 自定义未删除为0,已删除为2 private Integer status;
CRUD自动改写机制:底层SQL如何变魔术
当执行以下操作时,MyBatis-Plus会自动追加逻辑条件:
| 操作 | 原生SQL | MP改写后SQL |
|---|---|---|
deleteById(1L) |
DELETE FROM orders WHERE id=1 |
UPDATE orders SET deleted=1 WHERE id=1 AND deleted=0 |
selectById(1L) |
SELECT * FROM orders WHERE id=1 |
SELECT * FROM orders WHERE id=1 AND deleted=0 |
updateById(order) |
UPDATE orders SET ... WHERE id=? |
UPDATE orders SET ..., deleted=0 WHERE id=? AND deleted=0 |
注意:
insert操作不自动填充删除标记,需手动赋值或使用MyBatis-Plus自动填充功能。- 自定义SQL(
@Select、XML)默认不生效,需自行拼接AND deleted=0。
完整电商订单案例:用户侧与管理侧双视角
1 场景描述
- 用户操作:删除自己的历史订单(逻辑删除)
- 管理员操作:查看所有订单(包括已删除)、恢复误删订单
2 用户删除订单代码
@RestController
public class OrderController {
@Autowired
private OrderService orderService;
@DeleteMapping("/user/order/{id}")
public Result deleteOrder(@PathVariable Long id, @RequestParam Long userId) {
// 底层自动变为:UPDATE orders SET deleted=1 WHERE id=? AND user_id=? AND deleted=0
LambdaUpdateWrapper<Order> wrapper = new LambdaUpdateWrapper<>();
wrapper.eq(Order::getId, id).eq(Order::getUserId, userId);
return Result.ok(orderService.remove(wrapper));
}
}
3 管理员查询已删除订单 + 恢复数据
@GetMapping("/admin/order/deleted")
public Result<List<Order>> getDeletedOrders() {
// 绕过MP默认过滤,使用自定义SQL
return Result.ok(orderService.baseMapper.selectDeletedOrders());
}
// OrderMapper.xml
<select id="selectDeletedOrders" resultType="Order">
SELECT * FROM orders WHERE deleted = 1
</select>
@PostMapping("/admin/order/restore/{id}")
public Result restoreOrder(@PathVariable Long id) {
// 直接更新标记位
Order order = new Order();
order.setId(id);
order.setDeleted(0);
return Result.ok(orderService.updateById(order));
}
常见坑与性能优化技巧(进阶必读)
1 唯一索引冲突陷阱
问题:逻辑删除后,唯一约束(如订单编号order_no)仍存在,导致重新插入相同订单失败。
解决方案:
ALTER TABLE orders DROP INDEX uk_order_no, ADD INDEX uk_order_no (order_no, deleted); -- 联合唯一索引
2 联表查询必须手动过滤
// 错误写法:左连接仍会带出已删除记录
@Select("SELECT * FROM orders o LEFT JOIN payment p ON o.id=p.order_id")
// 正确写法:手动追加条件
@Select("SELECT * FROM orders o LEFT JOIN payment p ON o.id=p.order_id WHERE p.deleted=0 AND o.deleted=0")
3 分页查询的性能优化
建议在deleted字段上建立联合索引(如(user_id, deleted)),并定期物理清理deleted=1的历史数据(如定时任务),避免索引膨胀。
高频面试问答(FAQ)
Q1:逻辑删除后,如何统计“真实”的总记录数?
A:使用自定义SQL
SELECT COUNT(*) FROM orders,绕过MP自动追加的WHERE deleted=0。
Q2:MyBatis-Plus逻辑删除可以用于多字段标记吗(如deleted_by_user和deleted_by_admin)?
A:
@TableLogic仅支持单字段,多字段复杂场景建议结合@SqlParser注解或自定义拦截器处理。
Q3:如何实现“回收站”功能,支持批量恢复?
A:批量恢复时,直接使用
update设置deleted=0,并通过LambdaUpdateWrapper.in(Order::getId, idList)实现。
Q4:逻辑删除的字段名一定是deleted吗?
A:可以自定义,通过
@TableLogic(value="0", delval="1")或全局配置logic-delete-field指定任意字段。
Q5:逻辑删除会影响乐观锁(@Version)吗?
A:会,当执行
deleteById时,如果实体带@Version字段,MP会同时校验版本号,确保数据一致性。
结尾结语:逻辑删除是业务系统的必备技能,MyBatis-Plus将其简化到“一行配置”,但生产环境需结合唯一索引、联表查询、数据归档等综合设计,才能真正发挥“软删除”的威力,建议读者根据本文案例,在实际项目中尝试改造,并关注官方文档的版本更新(如3.5.x对逻辑删除的进一步优化)。