逻辑删除实战:MyBatis-Plus @TableLogic注解的优雅实现方案
引言:为什么需要逻辑删除?
在传统的数据删除操作中,我们通常使用物理删除(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);
}
审计信息自动填充
性能优化策略
索引设计建议
-- 为逻辑删除字段添加索引
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 注解为逻辑删除提供了极其优雅的实现方案:
- 零侵入设计:通过注解配置,不影响现有业务代码
- 自动过滤:所有查询操作自动过滤已删除数据
- 灵活配置:支持字段级和全局级配置
- 类型安全:支持多种数据类型配置
- 扩展性强:可结合审计、多租户等复杂场景
通过本文的实战方案,您可以快速在企业级应用中实现安全、可靠、易维护的逻辑删除功能,为数据安全和业务连续性提供坚实保障。
提示:在实际项目中,建议结合具体业务需求选择合适的删除策略,并建立完善的数据备份和恢复机制。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



