首页
/ TypeORM 自动生成迁移指南:用 migration:generate 从实体变更一键产出可回滚 SQL

TypeORM 自动生成迁移指南:用 migration:generate 从实体变更一键产出可回滚 SQL

2026-09-08 09:01:47作者:袁立春Spencer

TypeORM 提供了一条「自动生成迁移」的捷径:你只需要像平时开发一样修改实体(Entity),命令行工具会自动把实体与数据库中现有 Schema 做对比,把必须执行的 SQL 全部写进一个新迁移文件。本文基于 docs/docs/migrations/04-generating.md 展开,结合仓库中 MigrationGenerateCommand.ts 的实现与命令级测试,讲清 typeorm migration:generate 的完整用法、生成文件的结构,以及 --outputJs--pretty--dryrun--check 等选项在真实工作流中的价值。读完你就能把「手工编写 ALTER TABLE」这一步从日常迭代中彻底去掉。

自动生成迁移做了什么

TypeORM 自动生成迁移(Automatic migration generation)的核心逻辑是差值对比

  1. 读取你代码里当前定义的实体(Entity)与视图(View);
  2. 连接目标数据库,读取它当前实际存在的表结构与视图;
  3. 对比两边的差异(新增表、改名列、改类型、加索引、删约束……);
  4. 把差异换算成一组「升级 SQL」(up 查询)和一组「回滚 SQL」(down 查询);
  5. {TIMESTAMP}-{migration-name}.ts 的形式生成新的迁移文件,写入全部需要执行的 SQL。

如果没有任何差异,命令不会生成空文件,而是以退出码 1 结束,并提示你改用 migration:create 创建空白迁移手动填充——这个行为让 migration:generate 天然可以嵌入 CI,当作「数据库漂移检测器」使用。

从源码看对比过程

命令真正执行的底层调用链在 MigrationGenerateCommand.ts

const sqlInMemory = await dataSource.driver
    .createSchemaBuilder()
    .log()

不同驱动的 createSchemaBuilder()(参见 Driver.ts 的接口声明,以及 postgresmysqlmssqloraclebetter-sqlite3 等各驱动目录下的实现)会返回对应的 Schema Builder;对关系型数据库而言,实际执行对比的是 RdbmsSchemaBuilder.ts 中的 log() 方法:

async log(): Promise<SqlInMemory> {
    this.queryRunner = this.dataSource.createQueryRunner()
    try {
        const tablePaths = ...
        this.tables = await this.queryRunner.getTables(tablePaths)   // 读取数据库现有表
        this.views  = await this.queryRunner.getViews(viewPaths)     // 读取数据库现有视图
        this.queryRunner.enableSqlMemory()                           // 开启 SQL 内存捕获
        await this.executeSchemaSyncOperationsInProperOrder()        // 按顺序计算并"执行"同步操作
        return this.queryRunner.getMemorySql()                       // 取回 up/down 两组 SQL
    } finally { ... }
}

它先把数据库里的真实表/视图结构取出来,再以「SQL 内存模式」跑一遍 schema 同步逻辑,最终返回一个包含 upQueriesdownQueriesSqlInMemory 对象——这就是后面要写进迁移文件的两份 SQL 清单。整个过程并不会真正改动数据库,因此非常安全。

需要留意的一点是:在执行前,命令会对加载进来的 DataSource 强制改写选项MigrationGenerateCommand.ts):

dataSource.setOptions({
    synchronize: false,
    migrationsRun: false,
    dropSchema: false,
    logging: false,
})

也就是说,即使你本地的 DataSource 配置了 synchronize: truemigrationsRun: true,在生成迁移时也都会被强制关闭,避免自动同步干扰差值计算,也不会在生成过程中误跑历史迁移。

命令语法与参数说明

生成迁移的基础命令如下:

typeorm migration:generate -d <path/to/datasource> <migration-name>

其中:

  • <path/to/datasource>-d 参数的值必须指向定义了你 DataSource 实例的文件路径。关于 DataSource 的定义方式可参考 DataSource 指南
  • <migration-name>:本次迁移的名字,会拼在时间戳后面构成文件名与类名。

文档给出的三种等价写法

省略 typeorm 前缀的方式,直接生成:

typeorm migration:generate -d <path/to/datasource> <migration-name>

也可以通过 --name 参数指定名称:

typeorm migration:generate -- -d <path/to/datasource> --name=<migration-name>

或者直接使用完整路径(名称前的目录会作为迁移文件的输出目录):

typeorm migration:generate -d <path/to/datasource> <path/to/migrations>/<migration-name>

如果迁移文件放在 src/db/migrations 下、名为 post-refactoring,那么实际运行可能长这样:

typeorm migration:generate -d src/data-source.ts src/db/migrations/post-refactoring

生成结果是一个名为 {TIMESTAMP}-post-refactoring.ts 的文件,其中 {TIMESTAMP} 是生成时刻的时间戳(默认取 Date.now())。

当前 CLI 实现中的参数一览

从当前 CLI 的源码定义(MigrationGenerateCommand.ts)看,命令签名为 migration:generate <path>,迁移名/路径是一个必填的 positional 参数;完整支持以下选项:

选项 别名 类型 默认值 作用
<path>(位置参数) string 必填 迁移文件路径(可只给名字,也可给 目录/名字
--dataSource -d string 必填 定义 DataSource 实例的文件路径
--outputJs -o boolean false 输出 JavaScript(.js)而非 TypeScript(.ts
--esm boolean false -o 配合,输出 ESM 语法而非 CommonJS
--pretty -p boolean false 对生成的 SQL 做多行格式化,方便阅读
--dryrun -dr boolean false 只把迁移内容打印到终端,不写文件
--check -ch boolean false 校验数据库是否与实体一致,CI 场景下非常有用
--timestamp -t number 当前时间 为迁移文件指定自定义时间戳

其中 --timestamp 会被 CommandUtils.ts 中的 getTimestamp() 校验并处理:传入值必须是非负数字,否则会抛出 timestamp option should be a non-negative number 错误;不传则回退到 Date.now()

完整示例:把 Post 实体字段 title 改名为 name

假设你有一个带 title 列的 Post 实体,这次你把它改名为 name

// 修改前
export class Post {
    @PrimaryGeneratedColumn()
    id: number

    @Column()
    title: string
}

// 修改后
export class Post {
    @PrimaryGeneratedColumn()
    id: number

    @Column()
    name: string
}

运行生成命令(此处以 post-refactoring 作为迁移名):

typeorm migration:generate -d <path/to/datasource> post-refactoring

TypeORM 会生成新文件 {TIMESTAMP}-post-refactoring.ts,内容大致如下:

import { MigrationInterface, QueryRunner } from "typeorm"

export class PostRefactoringTIMESTAMP implements MigrationInterface {
    async up(queryRunner: QueryRunner): Promise<void> {
        await queryRunner.query(
            `ALTER TABLE "post" ALTER COLUMN "title" RENAME TO "name"`,
        )
    }

    async down(queryRunner: QueryRunner): Promise<void> {
        await queryRunner.query(
            `ALTER TABLE "post" ALTER COLUMN "name" RENAME TO "title"`,
        )
    }
}

可以看到 up 负责把 Schema 升级到新状态,down 是它的镜像,负责回滚——这两份 SQL 都是 TypeORM 依据实体与数据库的差值自动算出来的,不需要你手工编写。当然,具体 SQL 文案会随数据库方言而不同(PostgreSQL、MySQL、SQL Server、SQLite 等各有差异),上例是文档给出的示意,真实执行时请以生成结果为准。

整个迁移文件的拼装逻辑可以在源码模板方法里看到:类名由 camelCase(migrationName, true) + timestamp 组成,up 的查询顺序与 down 的查询顺序互为逆序(MigrationGenerateCommand.ts),生成出的类还带有 name = '{ClassName}' 属性供 TypeORM 识别迁移身份:

export class PostRefactoring1699000000000 implements MigrationInterface {
    name = 'PostRefactoring1699000000000'

    public async up(queryRunner: QueryRunner): Promise<void> {
        // upSqls...
    }

    public async down(queryRunner: QueryRunner): Promise<void> {
        // downSqls(顺序已反转)
    }
}

每条 SQL 在写入模板前还会经过 escapeTemplateLiteral() 处理:它会转义反斜杠、反引号与 ${,防止数据库元数据(比如列注释 COMMENT、默认值 DEFAULT)里的特殊字符破坏 JavaScript 模板字符串的语法。仓库里对应的 migration-generate-escape 测试(见 test/functional/commands)就是专门覆盖这类边界情况的。

面向纯 JavaScript 项目:把迁移输出为 JS 文件

如果项目是纯 JavaScript,没有安装 TypeScript 相关依赖,可以用 -o--outputJs 的别名)把迁移输出为 JavaScript 文件。命令如下:

typeorm migration:generate -d <path/to/datasource> -o post-refactoring

执行后生成 {TIMESTAMP}-PostRefactoring.js,默认采用 CommonJS 写法,并带上 JSDoc 类型标注:

/**
 * @typedef {import('typeorm').MigrationInterface} MigrationInterface
 * @typedef {import('typeorm').QueryRunner} QueryRunner
 */

/**
 * @class
 * @implements {MigrationInterface}
 */
module.exports = class PostRefactoringTIMESTAMP {
    /**
     * @param {QueryRunner} queryRunner
     */
    async up(queryRunner) {
        await queryRunner.query(
            `ALTER TABLE "post" ALTER COLUMN "title" RENAME TO "name"`,
        )
    }

    /**
     * @param {QueryRunner} queryRunner
     */
    async down(queryRunner) {
        await queryRunner.query(
            `ALTER TABLE "post" ALTER COLUMN "name" RENAME TO "title"`,
        )
    }
}

如果你的 JavaScript 项目使用 ESM 模块体系,可再叠加 --esm 标志生成 export 语法的版本:

typeorm migration:generate -d <path/to/datasource> -o --esm post-refactoring
/**
 * @typedef {import('typeorm').MigrationInterface} MigrationInterface
 * @typedef {import('typeorm').QueryRunner} QueryRunner
 */

/**
 * @class
 * @implements {MigrationInterface}
 */
export class PostRefactoringTIMESTAMP {
    /**
     * @param {QueryRunner} queryRunner
     */
    async up(queryRunner) {
        await queryRunner.query(
            `ALTER TABLE "post" ALTER COLUMN "title" RENAME TO "name"`,
        )
    }

    /**
     * @param {QueryRunner} queryRunner
     */
    async down(queryRunner) {
        await queryRunner.query(
            `ALTER TABLE "post" ALTER COLUMN "name" RENAME TO "title"`,
        )
    }
}

源码中的 getJavascriptTemplate() 正是通过一行 const exportMethod = esm ? "export" : "module.exports =" 来切换两种模块格式的,其余结构(JSDoc 注释、up/downname 属性)保持一致。

没有差异时怎么办:退出码与空迁移兜底

自动生成的前提是「实体与数据库之间存在差异」。如果你把实体改动同步进了数据库(比如开启了 synchronize),或者确实没改任何结构,命令会发现 upSqls 为空,此时:

  • 普通模式下,终端输出黄色提示:

    No changes in database schema were found - cannot generate a migration. To create a new empty migration use "typeorm migration:create" command
    

    并以退出码 1 结束;

  • 若此时确实需要一条迁移,请改用 手动创建迁移 中的 typeorm migration:create,得到空白的 up/down 骨架后自行填写 SQL;

  • 该行为同时意味着:把它放进 CI,任何「实体已改但没补迁移」的提交都会让流水线失败,从而守住「数据库变更必须有迁移文件」这条纪律。

其他实用选项与推荐工作流

--pretty:SQL 多行格式化

默认情况下,一句复杂的 DDL 会作为一条长模板字符串写进迁移文件,可读性较差。加上 -p--pretty 别名)后,生成前会调用 prettifyQuery() 把 SQL 拆成带缩进的多行,再嵌入迁移文件:

typeorm migration:generate -p -d <path/to/datasource> <migration-name>

--dryrun:只打印不落盘

在改动实体较多、想先预览一下会生成哪些 SQL 时,用 -dr 先跑一遍:

typeorm migration:generate --dryrun -d <path/to/datasource> <migration-name>

终端会把迁移文件内容完整打印出来(不写文件),确认无误后再去掉 --dryrun 正式生成。它与仓库中 schema:log 命令(见 SchemaLogCommand.ts)走的是同一条 createSchemaBuilder().log() 对比管线。

--check:数据库是否与实体一致

--check(别名 -ch)适合放进 CI 或代码提交前检查:

  • 没有任何差异时,输出绿色 No changes in database schema were found,进程以退出码 0 正常结束;
  • 存在预期之外的差异时,输出黄色提示并把完整迁移内容打印出来,进程以退出码 1 结束(MigrationGenerateCommand.ts)。

也就是说,migration:generate --check 是「这次改动是否需要迁移」的判断题,配合 查看迁移状态migration:show 一起使用,可以组成完整的 Schema 变更守卫。

--timestamp:自定义时间戳

需要精确控制迁移文件命名中的时间戳(例如与发布版本对齐)时,可用数字毫秒值指定:

typeorm migration:generate -d <path/to/datasource> -t 1610975184784 post-refactoring

它会生成形如 1610975184784-post-refactoring.ts 的文件。传入的值必须是合法的非负数字,否则 getTimestamp() 会直接抛错,这也是一个不错的参数校验范例。

推荐的迭代节奏

一个值得固化成习惯的规则是:每当你对模型(实体)做一次改动,就立即生成一条迁移。一次改一个模型、生成一条迁移,可以让每条迁移文件都小而聚焦,便于 review 与排障,也让 down 回滚的粒度保持清晰。生成的迁移通过 执行迁移migration:run 应用,出错时用 回滚迁移migration:revert 撤销;对单条迁移的精细控制还可以参考 迁移 API

测试如何保证生成正确

仓库对迁移生成逻辑有专门的命令级测试 test/functional/commands/migration-generate.test.ts,覆盖了三大行为:

  • 默认输出 TypeScript 迁移:不传任何选项时,生成的 .ts 文件内容与预期的 resultsTemplates 模板完全一致(通过 sinon stub 掉文件写入与 DataSource 加载来断言内容);
  • outputJs 输出 JavaScript:传入 outputJs: true 时生成 .js 文件,内容匹配 JavaScript 模板;
  • 自定义时间戳:传入固定 timestamp 后,文件名变为 {timestamp}-test-migration.ts

值得注意,该测试的 enabledDrivers 覆盖了 postgresmssqlmysqlmariadbbetter-sqlite3oraclecockroachdb 等主流数据库(migration-generate.test.ts),说明「实体 vs 数据库差值 → 生成迁移 SQL」的核心路径在不同方言上都被持续验证着。这也提醒我们:虽然本文示例统一用了 ALTER TABLE ... RENAME 的写法,但实际 SQL 必须由你连接的数据库驱动来决定。

小结

TypeORM 的 migration:generate 把「读实体、连数据库、算差值、写迁移」四步压缩成一条命令。核心要记住四点:

  1. 参数-d 必须指向导出了 DataSource 实例的文件,迁移名可以只写名字也可以带目录路径;
  2. 产物:默认生成 {TIMESTAMP}-{name}.ts,含 up/downname 属性,全部 SQL 由 createSchemaBuilder().log() 自动对比产出;
  3. 纯 JS 项目-o 输出 .js(CommonJS),叠加 --esm 切换为 ESM;
  4. 工程化用法--pretty 提升可读性、--dryrun 先预览、--check 守护 CI、无差异时以退出码 1 明确拒绝生成,并引导使用 migration:create

关于迁移的完整工作流(手动创建、执行、回滚、状态查看、假执行与 API 说明),可以按顺序阅读 01-why02-setup03-creating05-executing06-reverting07-status09-api,把自动生成真正融入你的数据库版本管理流程。

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.14 K
2.75 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
857
1.35 K
docsdocs
暂无描述
Markdown
898
5.82 K
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
921
1.84 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.8 K
1.02 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
531
596
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
1.02 K
519
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.36 K
1.46 K
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
548
391