Skip to content

逻辑删除与唯一键设计

Forge 默认使用逻辑删除。你在新建表或设计唯一索引时,必须理解 del_flag 的工作机制。

del_flag 机制

含义
0未删除(正常)
0已删除(删除时写入当前行主键)

为什么删除后写主键?

当你删除一条记录时,del_flag 会被设为该行的 id 值,而不是固定值 1。这样同一业务键可以多次删除并重建,唯一索引不会冲突。

sql
-- 删除前:del_flag = 0
-- 删除后:del_flag = 123(当前行主键)
-- 再创建同业务键记录:del_flag = 0,唯一索引不冲突

@TableLogic 注解

实体必须显式声明逻辑删除字段:

java
@TableLogic(value = "0", delval = "id")
@TableField("del_flag")
private Long delFlag;
属性说明
value未删除时的值
delval删除时的值,"id" 表示写入主键列

唯一索引设计

需要"删除后允许同值重建"的表,唯一索引必须包含 del_flag

sql
-- 正确:包含 del_flag
CREATE UNIQUE INDEX uk_tenant_username
ON sys_user(tenant_id, username, del_flag);

-- 禁止:缺少 del_flag,删除后无法重建
CREATE UNIQUE INDEX uk_tenant_username
ON sys_user(tenant_id, username);

不需要 del_flag 的场景

场景原因
无业务唯一键不需要唯一索引
业务键要求跨历史永久唯一已删除记录也阻塞重建

Mapper XML 查询

自定义 XML 查询必须显式过滤未删除数据,MyBatis-Plus 不会自动补全:

xml
<!-- 正确:显式过滤 -->
<select id="selectUserList" resultType="SysUserEntity">
    SELECT id, username, nickname
    FROM sys_user
    WHERE del_flag = 0
      AND tenant_id = #{tenantId}
</select>

<!-- 禁止:缺少 del_flag 条件 -->
<select id="selectUserList" resultType="SysUserEntity">
    SELECT id, username FROM sys_user
</select>

允许物理删除的场景

场景说明
Flowable/Quartz 框架表按框架语义处理
临时表/暂存表无恢复需求
日志归档清理留存策略物理清理

新增物理删除点必须在 Spec 中写明原因和回滚方式。

批量删除

批量删除使用批量更新,禁止循环逐条删除:

java
// 正确:批量更新
lambdaUpdate()
    .set(SysUserEntity::getDelFlag, entity.getId())
    .in(SysUserEntity::getId, ids)
    .update();

Forge Admin — 基于 Vue3 + Spring Boot 的企业级后台管理框架