逻辑删除实战:MyBatis-Plus @TableLogic注解的优雅实现方案

逻辑删除实战:MyBatis-Plus @TableLogic注解的优雅实现方案

【免费下载链接】mybatis-plus An powerful enhanced toolkit of MyBatis for simplify development 【免费下载链接】mybatis-plus 项目地址: https://gitcode.com/gh_mirrors/my/mybatis-plus

引言:为什么需要逻辑删除?

在传统的数据删除操作中,我们通常使用物理删除(Physical Delete)直接从数据库中移除记录。然而,这种操作存在诸多问题:

  • 数据丢失风险:误删数据后难以恢复
  • 审计追踪困难:无法追溯历史操作记录
  • 业务连续性影响:关联数据可能因此失效

逻辑删除(Logical Delete)通过标记删除状态而非真正移除数据,完美解决了这些问题。MyBatis-Plus 提供的 @TableLogic 注解让逻辑删除的实现变得异常简单和优雅。

@TableLogic 注解深度解析

注解定义与属性

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})
public @interface TableLogic {
    /**
     * 默认逻辑未删除值(该值可无、会自动获取全局配置)
     */
    String value() default "";

    /**
     * 默认逻辑删除值(该值可无、会自动获取全局配置)
     */
    String delval() default "";
}

核心特性

特性说明优势
运行时注解在运行时生效,支持动态配置灵活应对不同环境需求
字段级注解标注在实体类字段上精确控制删除逻辑
全局配置支持支持全局默认值配置统一团队开发规范
类型自适应支持多种数据类型兼容现有数据库设计

实战配置:三种部署方案

方案一:注解直接配置(推荐)

@Data
public class User implements Serializable {
    private Long id;
    private String name;
    
    @TableLogic(value = "0", delval = "1")
    private Integer deleted;
}

方案二:全局配置统一管理

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

方案三:混合配置策略

@Data
public class Product implements Serializable {
    private Long id;
    private String productName;
    
    // 使用全局配置,注解中不指定具体值
    @TableLogic
    private Boolean isDeleted;
}

完整实战示例

实体类设计

@Data
public class Order implements Serializable {
    private static final long serialVersionUID = 1L;
    
    @TableId(type = IdType.AUTO)
    private Long id;
    
    private String orderNo;
    private BigDecimal amount;
    
    @TableField(fill = FieldFill.INSERT)
    private LocalDateTime createTime;
    
    @TableField(fill = FieldFill.UPDATE)
    private LocalDateTime updateTime;
    
    @TableField(fill = FieldFill.UPDATE)
    private String deleteBy;
    
    @TableLogic(value = "false", delval = "true")
    @TableField(fill = FieldFill.UPDATE)
    private Boolean deleted;
}

数据库表结构

CREATE TABLE `order` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `order_no` varchar(64) NOT NULL COMMENT '订单编号',
  `amount` decimal(10,2) NOT NULL COMMENT '订单金额',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间',
  `delete_by` varchar(50) DEFAULT NULL COMMENT '删除人',
  `deleted` tinyint(1) DEFAULT '0' COMMENT '删除标志',
  PRIMARY KEY (`id`),
  KEY `idx_order_no` (`order_no`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单表';

Mapper 接口

public interface OrderMapper extends BaseMapper<Order> {
    // MyBatis-Plus 自动注入逻辑删除相关方法
    // 无需手动编写删除逻辑
}

业务层使用

@Service
@RequiredArgsConstructor
public class OrderService {
    private final OrderMapper orderMapper;

    // 逻辑删除订单
    public boolean deleteOrder(Long orderId, String operator) {
        Order order = new Order();
        order.setId(orderId);
        order.setDeleteBy(operator);
        return orderMapper.deleteById(order) > 0;
    }

    // 查询未删除的订单
    public List<Order> getActiveOrders() {
        return orderMapper.selectList(null); // 自动过滤已删除记录
    }

    // 查询所有订单(包含已删除)
    public List<Order> getAllOrders() {
        // 使用自定义wrapper绕过逻辑删除过滤
        return orderMapper.selectList(Wrappers.<Order>lambdaQuery()
            .apply("deleted = 0 or deleted = 1"));
    }
}

高级特性与最佳实践

自定义删除逻辑

public class CustomLogicDeleteHandler implements MetaObjectHandler {
    
    @Override
    public void updateFill(MetaObject metaObject) {
        strictUpdateFill(metaObject, "deleteBy", String.class, getCurrentUsername());
        strictUpdateFill(metaObject, "deleteTime", LocalDateTime.class, LocalDateTime.now());
    }
    
    private String getCurrentUsername() {
        // 获取当前登录用户
        return SecurityUtils.getCurrentUser();
    }
}

批量删除优化

@Mapper
public interface OrderMapper extends BaseMapper<Order> {
    
    /**
     * 自定义批量逻辑删除方法
     */
    @Delete("UPDATE order SET deleted = 1, delete_by = #{operator} WHERE id IN (#{ids})")
    int deleteBatchByIds(@Param("ids") List<Long> ids, @Param("operator") String operator);
}

审计信息自动填充

mermaid

性能优化策略

索引设计建议

-- 为逻辑删除字段添加索引
CREATE INDEX idx_deleted ON order(deleted);

-- 复合索引优化查询性能
CREATE INDEX idx_status_deleted ON order(status, deleted);

查询性能优化

// 避免全表扫描的查询方式
public Page<Order> queryOrders(OrderQuery query) {
    return orderMapper.selectPage(new Page<>(query.getPage(), query.getSize()),
        Wrappers.<Order>lambdaQuery()
            .eq(Order::getDeleted, false) // 明确指定未删除条件
            .eq(ObjectUtils.isNotEmpty(query.getStatus()), Order::getStatus, query.getStatus())
            .orderByDesc(Order::getCreateTime));
}

常见问题与解决方案

问题1:如何恢复已删除的数据?

public boolean restoreOrder(Long orderId) {
    return orderMapper.update(null, 
        Wrappers.<Order>lambdaUpdate()
            .set(Order::getDeleted, false)
            .set(Order::getDeleteBy, null)
            .eq(Order::getId, orderId)) > 0;
}

问题2:如何彻底删除数据?

@Transactional
public boolean physicalDeleteOrder(Long orderId) {
    // 先查询确保数据存在
    Order order = orderMapper.selectById(orderId);
    if (order == null) {
        return false;
    }
    
    // 使用自定义SQL进行物理删除
    orderMapper.physicalDeleteById(orderId);
    return true;
}

问题3:多租户环境下的逻辑删除

@Data
public class TenantOrder implements Serializable {
    private Long id;
    private String tenantId; // 租户ID
    
    @TableLogic(value = "0", delval = "1")
    private Integer deleted;
    
    // 确保查询时同时过滤租户和删除状态
    public static QueryWrapper<TenantOrder> queryWrapper(String tenantId) {
        return Wrappers.<TenantOrder>query()
            .eq("tenant_id", tenantId)
            .eq("deleted", 0);
    }
}

监控与维护

删除操作审计日志

@Aspect
@Component
@Slf4j
public class LogicDeleteAuditAspect {
    
    @Around("execution(* com..mapper.*.delete*(..))")
    public Object auditDeleteOperation(ProceedingJoinPoint joinPoint) throws Throwable {
        Object[] args = joinPoint.getArgs();
        String methodName = joinPoint.getSignature().getName();
        
        log.info("逻辑删除操作开始 - 方法: {}, 参数: {}", methodName, Arrays.toString(args));
        
        Object result = joinPoint.proceed();
        
        log.info("逻辑删除操作完成 - 方法: {}, 结果: {}", methodName, result);
        return result;
    }
}

定期清理策略

@Scheduled(cron = "0 0 2 * * ?") // 每天凌晨2点执行
public void cleanOldDeletedData() {
    LocalDateTime threshold = LocalDateTime.now().minusMonths(6);
    
    int count = orderMapper.physicalDeleteOldRecords(threshold);
    log.info("已清理{}条超过6个月的已删除订单记录", count);
}

总结

MyBatis-Plus 的 @TableLogic 注解为逻辑删除提供了极其优雅的实现方案:

  1. 零侵入设计:通过注解配置,不影响现有业务代码
  2. 自动过滤:所有查询操作自动过滤已删除数据
  3. 灵活配置:支持字段级和全局级配置
  4. 类型安全:支持多种数据类型配置
  5. 扩展性强:可结合审计、多租户等复杂场景

通过本文的实战方案,您可以快速在企业级应用中实现安全、可靠、易维护的逻辑删除功能,为数据安全和业务连续性提供坚实保障。

提示:在实际项目中,建议结合具体业务需求选择合适的删除策略,并建立完善的数据备份和恢复机制。

【免费下载链接】mybatis-plus An powerful enhanced toolkit of MyBatis for simplify development 【免费下载链接】mybatis-plus 项目地址: https://gitcode.com/gh_mirrors/my/mybatis-plus

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值