如何在TypeORM迁移出错时自动执行down方法回滚?
在NestJS+TypeORM中实现迁移失败自动回滚
我们在NestJS项目中使用TypeORM做数据库迁移,现有迁移脚本示例如下:
import {MigrationInterface, QueryRunner} from "typeorm"; export class VMTestTableTwo1713851442445 implements MigrationInterface { public async up(queryRunner: QueryRunner): Promise<void> { await queryRunner.query(` CREATE TABLE "vm_test"( "aggregate_id" uuid NOT NULL ); `); } public async down(queryRunner: QueryRunner): Promise<void> { await queryRunner.query(` DROP TABLE IF EXISTS "vm_test"; `); } }
理想状态下,若up脚本执行失败,应当自动执行down脚本完成回滚,但TypeORM迁移默认不支持该特性(Sequelize迁移具备此功能),请问如何在TypeORM中实现这一功能?
更新补充TypeORM配置:
export default TypeOrmModule.forRootAsync({ useFactory: (config: ConfigService) => { return { type: 'postgres', host: <host>, port: <port>, username: <username>, password: <pwd>, database: <db>, synchronize: false, logging: EnvChecker.queryLogger() ? ['query', 'error'] : false, migrationsRun: true, migrations: [ `${__dirname}/../migrations/*{.ts,.js}` ], autoLoadEntities: true, entities: [ `${__dirname}/../**/domain/entities/*.entity{.ts,.js}` ], extra: { max: <maxPool>, connectionTimeoutMillis: <connectionTimeoutMillis>, idleTimeoutMillis: <config.get('idleTimeoutMillis')> }, migrationsTransactionMode: 'each' }; }, inject: [ConfigService] });
解决方案
1. 利用事务自动回滚(基础方案)
你配置里已经设置了migrationsTransactionMode: 'each',这个配置会让TypeORM为每个迁移开启独立事务:
- 当
up脚本执行过程中抛出错误,事务会自动回滚,撤销up中已执行的所有操作,效果等价于自动执行down的核心逻辑(但不会主动调用down方法)。 - 这种方式无需修改迁移脚本,依赖数据库事务的原子性保证,适用于绝大多数常规迁移场景。
注意:如果迁移操作包含数据库不支持事务的语句(比如PostgreSQL的CREATE INDEX CONCURRENTLY),事务会失效,这种情况需要使用下面的自定义方案。
2. 自定义迁移基类封装回滚逻辑(进阶方案)
如果需要严格触发down方法完成回滚(比如迁移中包含非事务性操作),可以创建一个通用迁移基类,在up执行失败时主动调用down:
import { MigrationInterface, QueryRunner } from "typeorm"; export abstract class BaseMigration implements MigrationInterface { abstract up(queryRunner: QueryRunner): Promise<void>; abstract down(queryRunner: QueryRunner): Promise<void>; public async runUp(queryRunner: QueryRunner): Promise<void> { try { await this.up(queryRunner); } catch (error) { // 捕获up执行错误,调用down回滚 await this.down(queryRunner); // 重新抛出错误,让TypeORM标记迁移失败 throw error; } } }
随后让你的迁移类继承该基类,覆盖默认执行逻辑:
import { QueryRunner } from "typeorm"; import { BaseMigration } from "./base.migration"; export class VMTestTableTwo1713851442445 extends BaseMigration { public async up(queryRunner: QueryRunner): Promise<void> { await queryRunner.query(` CREATE TABLE "vm_test"( "aggregate_id" uuid NOT NULL ); `); } public async down(queryRunner: QueryRunner): Promise<void> { await queryRunner.query(` DROP TABLE IF EXISTS "vm_test"; `); } // 复用基类的回滚逻辑 public async runUp(queryRunner: QueryRunner): Promise<void> { return super.runUp(queryRunner); } }
3. 命令行手动回滚(应急方案)
如果上述方案无法覆盖你的场景,当迁移失败后,可以手动执行TypeORM回滚命令触发down:
# 回滚最后一次执行的迁移 npx typeorm-ts-node-esm migration:revert
内容的提问来源于stack exchange,提问作者XCEPTION
相关产品推荐
相关产品推荐

