逻辑删除与唯一键设计
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();