1. mybatis-plus逻辑删除机制解析
逻辑删除是现代数据库设计中常见的数据保留策略,它通过标记记录而非物理删除来保留数据完整性。mybatis-plus作为增强版的orm框架,提供了开箱即用的逻辑删除支持。与物理删除相比,逻辑删除具有三大核心优势:
- 数据可追溯性:保留完整业务历史记录
- 数据安全性:避免误删导致的数据丢失
- 系统稳定性:减少外键约束引发的级联问题
在技术实现层面,mybatis-plus通过动态sql改写机制实现逻辑删除。当执行删除操作时,框架会自动将delete语句转换为update语句。例如执行 usermapper.deletebyid(1) 时,实际执行的sql是:
update user set deleted = 1 where id = 1 and deleted = 0
2. 字段配置与默认值设置
2.1 数据库层配置规范
根据mysql最佳实践,建议采用以下建表语句配置逻辑删除字段:
create table `user` ( `id` bigint not null, `deleted` tinyint not null default 0 comment '0-未删除 1-已删除', primary key (`id`), index `idx_deleted` (`deleted`) ) engine=innodb default charset=utf8mb4
关键配置要点:
- 字段类型选择:推荐使用tinyint(1)或bit(1)作为布尔型标记
- 默认值必须设为0(未删除状态)
- 添加索引提升查询效率(特别是数据量大的表)
- 必须添加注释说明状态值含义
2.2 应用层配置方案
mybatis-plus提供两种配置方式:
方案一:全局配置(推荐)
mybatis-plus:
global-config:
db-config:
logic-delete-field: deleted # 实体类字段名
logic-delete-value: 1 # 删除标记值
logic-not-delete-value: 0 # 正常状态值方案二:注解配置
public class user {
@tablelogic(value = "0", delval = "1")
private integer deleted;
}
重要提示:当同时存在全局配置和注解配置时,注解配置优先级更高。建议团队统一采用一种配置方式以避免混乱。
3. 插入操作的特殊处理
3.1 默认值不生效的常见场景
即使数据库设置了default 0,以下情况仍可能导致逻辑删除字段值为null:
- 使用mybatis-plus的
insert()方法时显式传入了null值 - 数据库连接池配置了rewritebatchedstatements=true批量插入时
- 使用sql拼接方式插入数据
3.2 三种保障方案对比
| 方案 | 实现方式 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 数据库默认值 | 建表时设置default 0 | 完全透明,无需代码处理 | 依赖数据库实现 | 新建项目 |
| 自动填充 | 实现metaobjecthandler | 业务逻辑集中管理 | 增加框架依赖 | 已有项目改造 |
| 手动赋值 | 实体类set方法或builder | 灵活可控 | 容易遗漏 | 特殊业务逻辑 |
推荐方案:自动填充+数据库默认值双重保障
@component
public class mymetaobjecthandler implements metaobjecthandler {
@override
public void insertfill(metaobject metaobject) {
this.strictinsertfill(metaobject, "deleted", integer.class, 0);
}
}
4. 复杂场景下的实践技巧
4.1 多租户系统特殊处理
在多租户场景下,逻辑删除需要与租户id协同工作。建议在sql拦截器中添加额外条件:
public class tenantinterceptor implements innerinterceptor {
@override
public void beforequery(executor executor, mappedstatement ms,
object parameter, rowbounds rowbounds, resulthandler resulthandler,
boundsql boundsql) {
// 添加租户过滤条件
string sql = boundsql.getsql();
if(sql.contains("where")) {
sql += " and tenant_id = 'xxx' and deleted = 0";
}
}
}
4.2 审计字段与逻辑删除的协作
典型审计字段配置示例:
public class baseentity {
@tablefield(fill = fieldfill.insert)
private localdatetime createtime;
@tablefield(fill = fieldfill.update)
private localdatetime updatetime;
@tablelogic
private integer deleted;
@tablefield(fill = fieldfill.insert_update)
private long operatorid;
}
4.3 性能优化建议
- 查询优化:所有涉及逻辑删除表的查询都必须包含
deleted=0条件 - 索引策略:将逻辑删除字段作为复合索引的最后一列
- 归档方案:定期将已删除数据迁移到历史表
5. 常见问题排查指南
5.1 问题现象:逻辑删除失效
可能原因:
- 字段名称不匹配(检查yml配置与实体类字段名)
- 使用了自定义sql未添加逻辑删除条件
- 事务隔离级别导致(如repeatable_read下可能读取到旧数据)
解决方案:
// 方案1:使用mp原生方法
usermapper.deletebyid(1l);
// 方案2:自定义sql必须手动添加条件
@update("update user set deleted = 1 where id = #{id} and deleted = 0")
int logicdelete(@param("id") long id);
5.2 问题现象:唯一索引冲突
场景复现: 当唯一索引包含逻辑删除字段时,可能出现:
- 删除记录a(deleted=1)
- 新建记录a(与原有数据唯一键相同)
- 导致唯一约束冲突
解决方案:
- 修改唯一索引不包含逻辑删除字段
- 使用时间戳替代0/1状态:
alter table user add unique key uk_name (name, tenant_id);
6. 高级应用:逻辑删除扩展
6.1 多状态删除设计
对于需要区分删除原因的场景,可采用枚举扩展:
public enum deletestatus {
normal(0, "正常"),
user_deleted(1, "用户删除"),
admin_deleted(2, "管理员删除"),
system_deleted(3, "系统删除");
private final int code;
private final string desc;
}
6.2 回收站功能实现
基于逻辑删除构建回收站:
public interface recyclebinmapper {
@select("select * from user where deleted = 1 and update_time >= #{date}")
list<user> selectdeletedusers(localdatetime date);
@update("update user set deleted = 0 where id = #{id}")
int restoreuser(long id);
}
6.3 数据清理策略
建议制定明确的数据保留策略:
- 在线数据:deleted=0
- 回收站数据:deleted=1且update_time在30天内
- 归档数据:deleted=1且update_time超过30天
可通过定时任务实现自动清理:
@scheduled(cron = "0 0 3 * * ?")
public void cleandeleteddata() {
localdatetime deadline = localdatetime.now().minusdays(30);
usermapper.physicaldelete(deadline);
}
在实际项目中使用逻辑删除时,我总结出三个关键经验:第一,必须在项目初期明确数据生命周期管理策略;第二,所有团队成员的crud操作必须通过统一封装的basemapper进行;第三,定期使用 explain 分析包含逻辑删除条件的查询性能。这些实践能有效避免后期出现数据一致性问题。
到此这篇关于mybatis-plus逻辑删除机制的实践指南的文章就介绍到这了,更多相关mybatis-plus逻辑删除内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论