MyBatis-Plus逻辑删除案例

wen java案例 2

MyBatis-Plus逻辑删除实战指南:从配置到最佳实践(附完整案例)

目录导读

  1. 逻辑删除核心概念 – 为什么需要逻辑删除?物理删除的痛点分析
  2. MyBatis-Plus全局配置 – 一步步搭建逻辑删除环境(YAML/XML两种方式)
  3. 实体类注解详解@TableLogic的巧妙用法与字段设计规范
  4. CRUD自动改写机制 – 插入、查询、删除、更新如何被自动拦截
  5. 完整电商订单案例 – 用户删除订单、管理员查询已删除数据、恢复数据
  6. 常见坑与性能优化 – 唯一索引冲突、联表查询过滤、逻辑删除+分页
  7. 高频面试问答 – 逻辑删除 vs 物理删除、如何实现回收站功能

逻辑删除核心概念:业务数据的“软着陆”

在真实业务场景中,物理删除(DELETE) 会导致数据彻底消失,无法追溯审计、无法恢复误删数据,而逻辑删除通过增加一个标记字段(如deleted),将删除操作转换为UPDATE语句,让数据“隐身”而非“消失”。

MyBatis-Plus逻辑删除案例

痛点对比: | 场景 | 物理删除 | 逻辑删除 | |------|----------|----------| | 订单误删恢复 | ❌ 永久丢失 | ✅ 修改标记位即可 | | 数据库容量 | 较小 | 需定期清理垃圾数据 | | 查询性能 | 直接忽略 | 需追加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,且实体类字段名与配置一致,可省略@TableLogicMP自动识别

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:使用自定义SQLSELECT COUNT(*) FROM orders,绕过MP自动追加的WHERE deleted=0

Q2:MyBatis-Plus逻辑删除可以用于多字段标记吗(如deleted_by_userdeleted_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对逻辑删除的进一步优化)。

抱歉,评论功能暂时关闭!