MyBatis-Plus 逻辑删除实战:5种核心实现方案详解
在服务端开发中,数据安全与可追溯性至关重要。逻辑删除作为一种优雅的数据保护策略,通过标记字段而非物理删除来保留历史记录,已成为后端架构中的标配。MyBatis-Plus 作为流行的持久层框架,提供了灵活且强大的逻辑删除支持。本文将深入剖析5种核心实现方法,助你从容应对不同业务场景。
一、全局配置:统一规范的首选方案
对于大多数微服务项目,采用全局配置是最简洁高效的方案。只需在配置文件中定义逻辑删除的字段名、删除值与未删除值,所有继承自 BaseMapper 的接口将自动生效。
核心配置代码示例如下:
# application.yml
mybatis-plus:
global-config:
db-config:
logic-delete-field: deleted # 全局逻辑删除字段名
logic-delete-value: 1 # 逻辑已删除值(默认为1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为0)配置要点:
logic-delete-field:指定逻辑删除字段,如deleted,所有实体类共用此字段。logic-delete-value:删除时写入的值,通常为1或true。logic-not-delete-value:未删除时的默认值,通常为0或false。
配置完成后,删除操作会自动转换为 UPDATE 语句,而查询则会自动追加 deleted=0 条件。这种方式特别适合新项目或统一规范的后端架构,一次配置,全局生效。
二、实体类注解:灵活应对差异化需求
当不同数据表使用不同的逻辑删除字段或值时,全局配置就无法满足需求了。此时,可以在实体类的对应字段上使用 @TableLogic 注解进行精细化配置。
核心代码示例:
import com.baomidou.mybatisplus.annotation.TableLogic;
import com.baomidou.mybatisplus.annotation.TableName;
@Data
@TableName("user")
public class User {
private Long id;
private String name;
@TableLogic(value = "0", delval = "1")
private Integer deleted;
}
@Data
@TableName("product")
public class Product {
private Long id;
private String productName;
@TableLogic(value = "false", delval = "true")
private Boolean isDeleted;
}✅ 关键特性:
@TableLogic注解标注在逻辑删除字段上,支持Integer、Boolean、String等多种数据类型。value属性:未删除时的值,默认为"0"。delval属性:删除后的值,默认为"1"。
注解的优先级高于全局配置,非常适合老项目改造、多数据库表结构不一致或特殊业务需求。通过这种方式,可以在同一项目中实现不同表的逻辑删除差异化。
三、局部控制:通过自定义Wrapper覆盖默认行为
在某些特殊场景下,比如实现回收站功能或管理员查看所有数据,我们需要临时改变逻辑删除的行为。这时,可以通过自定义 Wrapper 来实现局部控制。
核心代码示例:
// 1. 查询时忽略逻辑删除条件
List users = userMapper.selectList(
Wrappers.lambdaQuery()
.apply("deleted = 1 or deleted = 0") // 自定义条件覆盖自动添加的条件
);
// 2. 使用自定义SQL完全控制
@Select("SELECT * FROM user WHERE id = #{id}")
User selectByIdIgnoreLogic(@Param("id") Long id);
// 3. 链式调用中的特殊处理
userMapper.selectList(
new QueryWrapper()
.eq("status", 1)
.and(wrapper -> wrapper.eq("deleted", 0).or().isNull("deleted"))
); 实现技巧:
- 使用
apply()方法可以添加原生 SQL 片段,覆盖自动生成的逻辑删除条件。 - 自定义 SQL 注解(如
@Select、@Update)不会自动添加逻辑删除条件,需要手动处理。 - 通过
or()和and()组合条件,可以实现灵活的控制逻辑。
这种方式在 API 层面提供了极高的可控性,尤其适合需要临时查看或恢复已删除数据的场景。
四、深度定制:SQL级别的过滤控制
MyBatis-Plus 默认自动过滤已删除数据,但在某些复杂业务中,我们需要更细粒度的控制。通过自定义 LogicSqlInjector 或使用 @SqlParser 注解的 filter 属性,可以实现 SQL 级别的深度定制。
核心配置与代码:
// 1. 配置类中自定义逻辑删除处理器
@Configuration
public class MybatisPlusConfig {
@Bean
public ISqlInjector sqlInjector() {
return new LogicSqlInjector() {
@Override
protected String getLogicDeleteSql(TableInfo tableInfo, boolean withWhere) {
// 自定义逻辑删除SQL生成逻辑
String logicDeleteField = tableInfo.getLogicDeleteField();
return "UPDATE " + tableInfo.getTableName() +
" SET " + logicDeleteField + " = #{et." + logicDeleteField + "}" +
(withWhere ? " WHERE " + tableInfo.getKeyColumn() + "=#{et." + tableInfo.getKeyProperty() + "}" : "");
}
};
}
}
// 2. 使用@SqlParser注解控制过滤
@SqlParser(filter = true) // 过滤逻辑删除数据
public List selectActiveUsers();
@SqlParser(filter = false) // 不过滤逻辑删除数据
public List selectAllUsers(); ⚠️ 注意事项:
- 可以在 Service 层、Mapper 层或具体方法上控制是否过滤逻辑删除数据。
- 通过 AOP 或拦截器,可以实现更复杂的过滤逻辑,比如根据用户角色动态调整。
- 这种方案适合需要精细权限控制的后端架构,确保数据安全。
五、多租户与逻辑删除的联合应用
在企业级微服务应用中,多租户数据隔离与逻辑删除常常需要结合使用。MyBatis-Plus 提供了无缝的集成方案,确保查询和删除操作同时考虑租户隔离与删除状态。
核心代码示例:
// 1. 多租户配置类
@Configuration
public class TenantConfig {
@Bean
public TenantLineHandler tenantLineHandler() {
return new TenantLineHandler() {
@Override
public String getTenantIdColumn() {
return "tenant_id";
}
@Override
public Expression getTenantId() {
return new LongValue(UserContext.getCurrentTenantId());
}
@Override
public boolean ignoreTable(String tableName) {
// 忽略不需要多租户的表
return "system_config".equals(tableName);
}
};
}
}
// 2. 实体类同时支持多租户和逻辑删除
@Data
@TableName("order")
public class Order {
private Long id;
private String orderNo;
@TableField(fill = FieldFill.INSERT) // 自动填充租户ID
private Long tenantId;
@TableLogic
private Integer deleted;
}
// 3. 查询时自动添加租户和逻辑删除条件
List orders = orderMapper.selectList(
Wrappers.lambdaQuery()
.eq(Order::getStatus, 1)
// 会自动添加:AND tenant_id = 当前租户ID AND deleted = 0
); 集成要点:
- 查询时会自动添加
tenant_id = ? AND deleted = 0条件,实现租户隔离。 - 删除操作会同时考虑租户隔离,避免跨租户误删数据。
- 通过
ignoreTable方法,可以排除不需要多租户的表,如公共配置表。
这种方案是构建安全、可扩展的企业级后端架构的关键技术。
[AFFILIATE_SLOT_1]最佳实践与性能优化
在实际项目中,除了选择合适的实现方式,还需要注意以下几点:
- 索引优化:逻辑删除字段建议添加索引,特别是数据量大的表,以提升查询性能。
- 字段类型选择:推荐使用
tinyint或boolean类型,占用空间小且查询效率高。避免使用字符串类型。 - 数据一致性:在批量操作或中间件集成时,确保逻辑删除字段的更新与业务逻辑一致。以下是一个示例:
// 事务中处理逻辑删除
@Transactional
public void deleteUserWithRelatedData(Long userId) {
// 删除用户
userMapper.deleteById(userId);
// 同时处理关联数据
userRoleMapper.deleteByUserId(userId);
// 记录操作日志
logService.saveDeleteLog(userId);
}- 查询性能:定期归档已删除数据,避免表数据量过大影响性能。
- 兼容性考虑:老数据迁移时,需要批量更新逻辑删除字段,确保新老数据格式一致。
总结
MyBatis-Plus 的逻辑删除功能设计巧妙且灵活,通过5种不同的实现方法,可以满足从简单到复杂的各种业务场景:
- 全局配置:适合项目统一规范,配置简单,一劳永逸。
- 注解配置:支持差异化配置,灵活性强,适合老项目改造。
- 局部控制:满足特殊业务需求,可控性高,适合回收站等功能。
- 深度定制:支持 SQL 级别控制,扩展性好,适合精细权限管理。
- 多租户集成:适合企业级应用,安全性强,是微服务架构的必备技能。
| 方法 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 默认自动过滤 | 常规业务查询 | 自动生效,无需额外配置 | 无法查询已删除数据 |
| @InterceptorIgnore | 特殊场景查询已删除 | 灵活控制单次查询 | 需要写注解或自定义SQL |
| 条件构造器 | 复杂条件查询 | 可精确控制删除状态 | 需要手动添加条件 |
| 自定义SQL | 完全自定义需求 | 灵活性最高 | 维护成本较高 |
| SQL注入器 | 框架扩展需求 | 可复用性强 | 实现复杂度高 |
在实际开发中,建议根据项目规模和复杂度选择合适的方案。逻辑删除不仅能保护数据安全,还能为数据恢复、审计追踪等功能提供支持,是现代服务端应用开发中不可或缺的重要特性。掌握这些技巧,将让你的后端架构更加健壮与优雅。
浙公网安备 33010602011771号