Typeorm 之软删除
最近在使用typeorm框架进行数据库操作时,准备用到软删除的功能,将使用过程以及遇到的问题记录下来,以作分享。
ORM框架
ORM - Object Relational Mapping,中文翻译为对象关系映射,简单来讲就是通过实例对象的语法,完成关系型数据库的操作。本质上就是将数据库映射成对象。
数据库的表(table) --> 类(class)
记录(record,行数据)--> 对象(object)
字段(field)--> 对象的属性(attribute)
有兴趣的可参考,阮一峰 ORM实例教程
不同的编程语言,有不同的ORM框架。例如Java的Hibernate,Ibatis/Mybatis等,Node的Sequelize,Typeorm等。
Typeorm简单介绍
官网链接
中文链接
git仓库
官网介绍如下:
TypeORM 是一个ORM框架,它可以运行在 NodeJS、Browser、Cordova、PhoneGap、Ionic、React Native、Expo 和 Electron 平台上,可以与 TypeScript 和 JavaScript (ES5,ES6,ES7,ES8)一起使用。 它的目标是始终支持最新的 JavaScript 特性并提供额外的特性以帮助你开发任何使用数据库的(不管是只有几张表的小型应用还是拥有多数据库的大型企业应用)应用程序。
目前,已更新到0.3.17,我是结合nestjs的框架使用(以下的介绍也都是基于nestjs中的使用经历),其在0.2.x和0.3.x之间有很大的使用区别,后续有机会可以专门在介绍下。
举个简单的例子,在生产环境migration的时候,0.3.x的版本,需要指定数据库的连接,如下图,
但是在,0.2.x的版本中却无需指定,如下图
上述截图均来自文档链接,中文文档的更新速度落后于英文文档,所以最好有问题,直接参考英文文档。
数据库软删除
所谓软删除,就是在数据库某表中删除某行数据时,并不是真的从表中删除,而是通过增加新的字段,单独进行标记。例如每个Entity中都包含IsActive字段,每次删除时,将其至false,如下:
@Column({ nullable: false, default: true })
isActive: boolean;
或者使用,typeorm自己封装好的装饰器@DeleteDateColumn,增加deletedTime列,如下
@DeleteDateColumn({
nullable: false,
})
deletedTime: Date;
每次,进行软删除时,typeorm会自动填充该条记录的删除时间,并且你在正常查询的时候,也不会返回已经被软删除的数据。
此外,还可通过restore恢复被软删除的数据,
/**
* Restores entities by a given criteria.
* Unlike save method executes a primitive operation without cascades, relations and other operations included.
* Executes fast and efficient SOFT-DELETE query.
* Does not check if entity exist in the database.
*/
restore(criteria: string | string[] | number | number[] | Date | Date[] | ObjectId | ObjectId[] | FindOptionsWhere<Entity>): Promise<UpdateResult>;
软删除方法介绍
softDelete和softRemove。如下:
/**
* Records the delete date of a given entity.
*/
softRemove<T extends DeepPartial<Entity>>(entity: T, options?: SaveOptions): Promise<T & Entity>;
/**
* Records the delete date of entities by a given criteria.
* Unlike save method executes a primitive operation without cascades, relations and other operations included.
* Executes fast and efficient SOFT-DELETE query.
* Does not check if entity exist in the database.
*/
softDelete(criteria: string | string[] | number | number[] | Date | Date[] | ObjectId | ObjectId[] | FindOptionsWhere<Entity>): Promise<UpdateResult>;
两者的区别如下:
- 函数参数不同,softDelete可传入id,ids,或FindOptionsWhere,而softRemove需要传入整个Entity或Entities。
- 返回结果不同,softDelete返回结果是UpdateResult,也可从侧面反映软删除其实就是更新了deletedTime这一列的值而已,而softRemove的返回值,是你操作的整个Entity或Entities。
想要查询操作时,一并得出被软删除的数据,可在查询条件FindOptionsWhere中,增加withDeleted:true
/**
* Indicates if soft-deleted rows should be included in entity result.
*/
withDeleted?: boolean;
数据库硬删除
到这里,简单提一下硬删除,和软删除相反,硬删除其实就是完全删除数据库中的某条记录,不可恢复,也是我们最常用操作。
方法如下:
delete和remove
/**
* Removes a given entity from the database.
*/
remove(entity: Entity, options?: RemoveOptions): Promise<Entity>;
/**
* Deletes entities by a given criteria.
* Unlike save method executes a primitive operation without cascades, relations and other operations included.
* Executes fast and efficient DELETE query.
* Does not check if entity exist in the database.
*/
delete(criteria: string | string[] | number | number[] | Date | Date[] | ObjectId | ObjectId[] | FindOptionsWhere<Entity>): Promise<DeleteResult>;
使用方法和区别与软删除类似,再次不做赘述,具体可以参考其函数说明。
硬删除和软删除该如何选择
首先,有个需求前提,用户需不需要对数据进行回滚操作。
因为,软删除的数据,并不是真的会从数据库中删除,只是通过某个字段进行了特别标记,它依然会存在于表中占据内存。如果在进行读取操作的时候,没有剔除标记字段,还会造成结果错误。而且,长期运行下来,如果数据库中堆积的软删除的数据过多,会严重影响数据库的操作性能。
另外,还有个比较重要的问题,级联删除。
软删除之级联删除
首先,简单介绍下硬删除的级联删除,在删除一个数据记录的时候,数据库会根据该数据与其它记录之间的关联关系来自动完成对其它关联记录的操作,例如,删除主表的字段后,与之级联的子表以及与子表级联的子表中的数据都会被自动删除。
但软删除的操作逻辑,其实是对数据进行更新操作,与其级联的表并不会有任何更改,更不用说与子表级联的子表。
在nestjs中,找到了一种软删除的方法(需要使用softRemove方法),可以在软删除主表时,同时软删除与之级联的子表,但与其子表级联的子表就没有便捷的方法进行软删除操作了。
const demoData = await this.demoRepository.findOne({ where: { id: id }, relations: ['changelogs'] });
await this.dbScriptRepository.softRemove(demoData);
其原理是通过,级联查询出所有的数据,然后使用软删除方法进行级联删除,如果要软删除与子表级联的子表,则需要自己把所有数据查询出来,然后再次使用软删除的方法。
即便完成了级联软删除的操作,使用restore进行数据恢复时,依然会比较麻烦。
所以,在进行软删除的选择时,如果需要对数据进行回滚,则需要尽量将数据表结构的级联关系设计的简单点。