首页
/ 彻底告别数据丢失:TypeORM软删除@DeleteDateColumn实现逻辑删除的终极指南

彻底告别数据丢失:TypeORM软删除@DeleteDateColumn实现逻辑删除的终极指南

2026-02-05 05:14:10作者:羿妍玫Ivan

你是否还在为误删生产数据而彻夜难眠?是否担心用户删除操作导致历史数据永久丢失?TypeORM的软删除功能通过@DeleteDateColumn装饰器提供了安全高效的解决方案,让数据恢复变得轻而易举。本文将系统讲解软删除的实现原理、操作方法和最佳实践,读完你将能够:

  • 理解逻辑删除与物理删除的核心差异
  • 掌握@DeleteDateColumn装饰器的完整用法
  • 熟练运用softRemove/restore等API进行数据操作
  • 解决软删除与查询、关联关系的常见问题
  • 构建符合企业级标准的数据安全保障体系

软删除的本质:从物理删除到逻辑标记

在传统开发中,删除操作通常意味着从数据库中永久移除记录(物理删除),这种方式虽然简单直接,但存在严重的数据安全隐患。TypeORM提供的软删除(逻辑删除)机制通过在实体中添加删除时间戳字段,实现了"假删除"效果——记录依然保留在数据库中,但查询时会自动过滤已标记删除的记录。

软删除与物理删除对比

工作原理剖析

软删除的核心实现位于@DeleteDateColumn装饰器中,其源码定义如下:

// src/decorator/columns/DeleteDateColumn.ts
export function DeleteDateColumn(options?: ColumnOptions): PropertyDecorator {
    return function (object: Object, propertyName: string) {
        getMetadataArgsStorage().columns.push({
            target: object.constructor,
            propertyName: propertyName,
            mode: "deleteDate",
            options: options || {},
        } as ColumnMetadataArgs)
    }
}

当实体被标记为软删除时,TypeORM会自动完成三项关键操作:

  1. 在数据库表中添加删除时间戳字段(默认为deletedAt
  2. 执行删除操作时更新此字段为当前时间而非移除记录
  3. 所有查询操作自动附加deletedAt IS NULL条件

快速上手:10分钟实现软删除功能

1. 实体定义与装饰器配置

首先需要在实体类中添加@DeleteDateColumn装饰器,定义删除时间字段:

import { Entity, PrimaryGeneratedColumn, Column, DeleteDateColumn } from "typeorm"

@Entity("users")
export class User {
    @PrimaryGeneratedColumn()
    id: number

    @Column()
    name: string

    @Column()
    email: string

    // 软删除核心字段
    @DeleteDateColumn({
        name: "deleted_at", // 数据库列名
        type: "timestamp",  // 字段类型
        nullable: true,     // 允许为NULL(未删除状态)
        comment: "记录删除时间戳,NULL表示未删除"
    })
    deletedAt: Date | null
}

字段命名建议:推荐使用deletedAt(默认)或deleted_at(下划线风格),保持与createdAt/updatedAt的命名一致性。详细配置选项可参考官方文档

2. 基础操作API全解析

TypeORM在Repository类中提供了完整的软删除操作方法,主要包括:

// src/repository/Repository.ts 核心API定义
// 软删除单个实体
softRemove(entity: T, options?: SaveOptions): Promise<T & Entity>

// 软删除多个实体
softRemove(entities: T[], options?: SaveOptions): Promise<(T & Entity)[]>

// 恢复已软删除的实体
recover(entity: T, options?: SaveOptions): Promise<T & Entity>

// 按条件批量软删除
softDelete(criteria: FindOptionsWhere<Entity>): Promise<UpdateResult>

// 按条件批量恢复
restore(criteria: FindOptionsWhere<Entity>): Promise<UpdateResult>

基本使用示例

// 软删除单个用户
const user = await userRepository.findOneBy({ id: 1 })
await userRepository.softRemove(user)

// 批量软删除
await userRepository.softDelete({ isActive: false })

// 恢复已删除用户
await userRepository.restore({ id: 1 })

// 查询未删除用户(自动过滤已删除记录)
const activeUsers = await userRepository.find()

深入原理:软删除的实现机制

装饰器工作流程

@DeleteDateColumn装饰器的工作流程可通过以下时序图展示:

sequenceDiagram
    participant 开发者
    participant 装饰器
    participant 元数据存储
    participant 数据库

    开发者->>装饰器: @DeleteDateColumn()
    装饰器->>元数据存储: 注册deleteDate类型字段
    Note over 元数据存储: 存储在ColumnMetadataArgs数组
    
    开发者->>装饰器: 执行softRemove()
    装饰器->>元数据存储: 读取deleteDate字段配置
    装饰器->>数据库: UPDATE SET deletedAt = NOW()
    数据库-->>装饰器: 返回更新结果
    装饰器-->>开发者: 返回更新后的实体

查询自动过滤实现

TypeORM在构建查询时会自动为包含@DeleteDateColumn的实体添加过滤条件,其实现逻辑类似于:

// 自动附加的查询条件
function applySoftDeleteFilter(qb: SelectQueryBuilder<any>, metadata: EntityMetadata) {
    if (metadata.hasDeleteDateColumn) {
        qb.andWhere(`${qb.alias}.${metadata.deleteDateColumn.databaseName} IS NULL`)
    }
}

最佳实践:企业级软删除解决方案

1. 实体设计规范

推荐的实体结构

@Entity("products")
export class Product {
    @PrimaryGeneratedColumn("uuid")
    id: string

    @Column()
    name: string

    @Column("decimal", { precision: 10, scale: 2 })
    price: number

    @CreateDateColumn({ name: "created_at" })
    createdAt: Date

    @UpdateDateColumn({ name: "updated_at" })
    updatedAt: Date

    @DeleteDateColumn({ name: "deleted_at" })
    deletedAt: Date | null

    // 添加索引提升查询性能
    @Index("idx_product_deleted_at", { where: "deleted_at IS NULL" })
    deletedAtIndex: never
}

性能优化建议:为deletedAt字段创建部分索引(Partial Index),只对未删除记录建立索引,可显著提升查询效率。

2. 复杂查询场景处理

包含已删除记录的查询

有时需要查询包含已删除记录的数据(如数据统计、审计日志),可使用withDeleted()方法:

// 查询所有用户(包括已删除)
const allUsers = await userRepository.find({ withDeleted: true })

// 仅查询已删除用户
const deletedUsers = await userRepository.find({
    withDeleted: true,
    where: { deletedAt: Not(null) }
})

// 查询构建器方式
const users = await userRepository.createQueryBuilder("user")
    .withDeleted()
    .where("user.name LIKE :name", { name: "%test%" })
    .getMany()

关联关系中的软删除处理

在一对多/多对多关系中,需要特别注意关联实体的软删除状态:

@Entity("orders")
export class Order {
    @PrimaryGeneratedColumn()
    id: number

    @ManyToOne(() => User, user => user.orders)
    user: User

    @OneToMany(() => OrderItem, item => item.order, {
        // 级联软删除
        onDelete: "SET NULL"
    })
    items: OrderItem[]
}

警告:软删除不会自动级联应用到关联实体,需要手动处理关联数据的删除状态。

3. 审计日志与数据追踪

结合TypeORM的事件监听功能,可以实现完整的删除审计:

@EntitySubscriber()
export class UserSubscriber implements EntitySubscriberInterface<User> {
    listenTo() {
        return User
    }

    afterSoftRemove(event: SoftRemoveEvent<User>) {
        // 记录删除日志
        const log = new AuditLog()
        log.entityType = "User"
        log.entityId = event.entity.id
        log.action = "SOFT_DELETE"
        log.performedBy = event.entity.modifiedBy
        log.timestamp = new Date()
        
        return event.manager.save(log)
    }
}

常见问题与解决方案

问题1:查询时如何排除软删除条件?

解决方案:使用查询构建器的withDeleted()方法:

const allRecords = await repository.createQueryBuilder("entity")
    .withDeleted() // 包含已删除记录
    .getMany()

问题2:软删除与唯一索引冲突

场景:当邮箱等字段设置唯一索引时,软删除用户后无法创建相同邮箱的新用户。

解决方案:创建包含deletedAt的复合唯一索引:

@Index(["email", "deletedAt"], { unique: true, where: "deletedAt IS NULL" })
@Column({ unique: false }) // 关闭单列唯一约束
email: string

问题3:如何批量软删除与恢复?

解决方案:使用softDeleterestore方法:

// 批量软删除
await userRepository.softDelete({ lastLoginAt: LessThan(new Date("2023-01-01")) })

// 批量恢复
await userRepository.restore({ id: In([1, 2, 3]) })

总结与展望

软删除作为数据安全的第一道防线,在TypeORM中通过@DeleteDateColumn实现了优雅而强大的解决方案。它不仅避免了数据丢失风险,还为数据分析、审计追踪提供了可能。随着TypeORM的不断发展,未来软删除功能可能会支持更多高级特性,如多版本控制、删除原因记录等。

建议所有涉及用户数据、交易记录的系统都强制实施软删除机制,配合定期备份策略,构建完整的数据安全保障体系。立即在项目中应用@DeleteDateColumn,让数据安全不再是奢侈品!

扩展学习资源

  • 官方文档:实体装饰器
  • API参考:Repository类
  • 高级示例:软删除测试用例
  • 数据库设计:软删除最佳实践
登录后查看全文
热门项目推荐
相关项目推荐