MySQL+NestJS+TypeORM用户注销时保留cat表删其他关联数据方案
NestJS+TypeORM+MySQL 下可扩展的用户注销数据清理方案
核心思路
放弃硬编码逐表写删除语句的逻辑,利用TypeORM本身维护的实体关联元数据、或MySQL系统表自动识别所有和User存在关联的表,通过配置排除需要保留数据的表(如Cat表),自动生成并执行删除逻辑,后续新增/删除关联表时无需修改核心清理代码。
前置注意事项
不要给所有外键统一配置ON DELETE CASCADE,会无差别删除Cat表的关联数据,不符合需求。可以单独给Cat表的userId外键配置ON DELETE SET NULL,删除用户时Cat表数据保留,仅将关联用户字段置空;其他需要随用户删除的关联表不要配置级联删除规则,统一由业务代码控制清理逻辑。
推荐实现:TypeORM元数据驱动自动清理
这个方案完全基于TypeORM的实体定义能力,不需要额外维护表名列表,新增关联实体时只要不加入保留列表就会被自动清理。
- 核心实现代码(可直接作为NestJS服务注入使用)
import { Injectable } from '@nestjs/common'; import { InjectConnection } from '@nestjs/typeorm'; import { Connection } from 'typeorm'; import { User } from './entities/user.entity'; import { Cat } from './entities/cat.entity'; @Injectable() export class UserCleanupService { // 配置用户删除时需要保留数据的实体,后续有其他要保留的表直接加到这个数组即可 private readonly retainedEntities = [Cat]; constructor(@InjectConnection() private readonly connection: Connection) {} async deleteUserAndRelatedData(userId: number): Promise<void> { const queryRunner = this.connection.createQueryRunner(); await queryRunner.connect(); await queryRunner.startTransaction(); try { const userMeta = this.connection.getMetadata(User); // 筛选所有和User关联、且不在保留列表的实体 const entitiesNeedClean = userMeta.relations .map(rel => rel.inverseEntityMetadata) .filter( entityMeta => !this.retainedEntities.some( retained => entityMeta.target === retained ) ); // 逐表执行删除 for (const entityMeta of entitiesNeedClean) { // 自动识别实体上关联User的外键字段名,不需要硬编码userId const userForeignKey = entityMeta.relations.find( rel => rel.inverseEntityMetadata.target === User )?.foreignKeys[0]?.columns[0]?.databaseName; if (!userForeignKey) continue; await queryRunner.manager .createQueryBuilder() .delete() .from(entityMeta.tableName) .where(`${userForeignKey} = :userId`, { userId }) .execute(); } // 所有关联数据清理完成后,删除User主表记录 await queryRunner.manager.delete(User, userId); await queryRunner.commitTransaction(); } catch (err) { await queryRunner.rollbackTransaction(); throw err; } finally { await queryRunner.release(); } } }
- 扩展性优化
如果后续有特殊清理需求的表(比如不需要物理删除、只做数据脱敏/软删除),可以自定义实体装饰器标记清理策略,遍历实体时识别装饰器走对应逻辑即可,不需要修改核心清理流程。
备选实现:基于MySQL系统表动态获取关联表
如果项目中部分表没有通过TypeORM配置关联关系,可以直接查询MySQL自带的information_schema系统表,自动获取所有关联User的表,排除保留表后执行删除:
-- 查询所有通过userId外键关联User表、且需要清理的表 SELECT TABLE_NAME FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WHERE REFERENCED_TABLE_NAME = 'user' AND REFERENCED_COLUMN_NAME = 'id' AND COLUMN_NAME = 'userId' AND TABLE_NAME != 'cat';
代码中拿到查询返回的表名列表后,循环拼接删除语句执行即可,注意同样要放在事务中保证数据一致性。
关键注意点
- 所有清理操作必须包裹在数据库事务中,避免中途报错导致部分数据删除、部分保留的脏数据问题
- 保留实体列表直接引用实体类,不要硬编码表名字符串,避免后续修改表名/实体名时出现配置遗漏
- 大用户量场景下如果关联表数据量过大,建议把删除逻辑改成分批异步删除,避免长事务锁表影响线上业务
内容的提问来源于stack exchange,提问作者user10874312
相关产品推荐
相关产品推荐

