软删除
大约 2 分钟
软删除
软删除通过在表中加入 deleted_at
(可空时间戳)实现“逻辑删除”。常见约定:
- 非删除:
deleted_at is null
- 已删除:
deleted_at is not null
表结构与实体
迁移中添加列:
.col(ColumnDef::new(SysUser::DeletedAt).timestamp_with_time_zone())
实体字段:
#[sea_orm(column_type = "TimestampWithTimeZone")]
pub deleted_at: Option<chrono::DateTime<chrono::Utc>>;
标记删除(Soft Delete)
use sea_orm::{EntityTrait, ActiveModelTrait, Set};
pub async fn soft_delete_user(db: &sea_orm::DatabaseConnection, id: i64) -> anyhow::Result<()> {
if let Some(m) = entity::sys_user::Entity::find_by_id(id).one(db).await? {
let mut am = m.into_active_model();
am.deleted_at = Set(Some(chrono::Utc::now()));
am.update(db).await?;
}
Ok(())
}
恢复(Restore)
pub async fn restore_user(db: &sea_orm::DatabaseConnection, id: i64) -> anyhow::Result<()> {
if let Some(m) = entity::sys_user::Entity::find_by_id(id).one(db).await? {
let mut am = m.into_active_model();
am.deleted_at = Set(None);
am.update(db).await?;
}
Ok(())
}
查询默认过滤
Sea-ORM 没有“全局作用域”的内建概念。建议在仓储/服务层封装过滤器:
use sea_orm::{QueryFilter, ColumnTrait, Select};
pub trait NotDeletedExt: Sized {
fn not_deleted(self) -> Self;
}
impl NotDeletedExt for sea_orm::Select<entity::sys_user::Entity> {
fn not_deleted(self) -> Self {
self.filter(entity::sys_user::Column::DeletedAt.is_null())
}
}
// 用法
let users = entity::sys_user::Entity::find().not_deleted().all(&db).await?;
或封装成通用函数:
fn not_deleted_cond() -> sea_orm::Condition {
sea_orm::Condition::all().add(entity::sys_user::Column::DeletedAt.is_null())
}
包含/仅看已删除
- 包含已删除:不加过滤
- 仅看已删除:
.filter(entity::sys_user::Column::DeletedAt.is_not_null())
与唯一约束
- 若用户名需要在“非删除数据”内唯一,可使用部分索引(PostgreSQL):
CREATE UNIQUE INDEX uniq_username_alive ON sys_user(username) WHERE deleted_at IS NULL;
- MySQL 可用触发器或在写入前校验
小结
- 推荐使用软删除以保留审计信息
- 封装
not_deleted()
以减少重复 - 唯一约束需考虑已删除记录的影响