
TypeORM 自动生成迁移指南用 migration:generate 从实体变更一键产出可回滚 SQL【免费下载链接】typeormTypeScript JavaScript ORM for Node.js — supports PostgreSQL, MySQL, MariaDB, SQLite, SQL Server, Oracle, and more.项目地址: https://gitcode.com/GitHub_Trending/ty/typeormTypeORM 提供了一条「自动生成迁移」的捷径你只需要像平时开发一样修改实体Entity命令行工具会自动把实体与数据库中现有 Schema 做对比把必须执行的 SQL 全部写进一个新迁移文件。本文基于 docs/docs/migrations/04-generating.md 展开结合仓库中 MigrationGenerateCommand.ts 的实现与命令级测试讲清typeorm migration:generate的完整用法、生成文件的结构以及--outputJs、--pretty、--dryrun、--check等选项在真实工作流中的价值。读完你就能把「手工编写 ALTER TABLE」这一步从日常迭代中彻底去掉。自动生成迁移做了什么TypeORM 自动生成迁移Automatic migration generation的核心逻辑是差值对比读取你代码里当前定义的实体Entity与视图View连接目标数据库读取它当前实际存在的表结构与视图对比两边的差异新增表、改名列、改类型、加索引、删约束……把差异换算成一组「升级 SQL」up 查询和一组「回滚 SQL」down 查询以{TIMESTAMP}-{migration-name}.ts的形式生成新的迁移文件写入全部需要执行的 SQL。如果没有任何差异命令不会生成空文件而是以退出码1结束并提示你改用migration:create创建空白迁移手动填充——这个行为让migration:generate天然可以嵌入 CI当作「数据库漂移检测器」使用。从源码看对比过程命令真正执行的底层调用链在 MigrationGenerateCommand.tsconst sqlInMemory await dataSource.driver .createSchemaBuilder() .log()不同驱动的createSchemaBuilder()参见 Driver.ts 的接口声明以及postgres、mysql、mssql、oracle、better-sqlite3等各驱动目录下的实现会返回对应的 Schema Builder对关系型数据库而言实际执行对比的是 RdbmsSchemaBuilder.ts 中的log()方法async log(): PromiseSqlInMemory { 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 同步逻辑最终返回一个包含upQueries与downQueries的SqlInMemory对象——这就是后面要写进迁移文件的两份 SQL 清单。整个过程并不会真正改动数据库因此非常安全。需要留意的一点是在执行前命令会对加载进来的 DataSource强制改写选项MigrationGenerateCommand.tsdataSource.setOptions({ synchronize: false, migrationsRun: false, dropSchema: false, logging: false, })也就是说即使你本地的 DataSource 配置了synchronize: true或migrationsRun: 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 --namemigration-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-dstring必填定义 DataSource 实例的文件路径--outputJs-obooleanfalse输出 JavaScript.js而非 TypeScript.ts--esm—booleanfalse与-o配合输出 ESM 语法而非 CommonJS--pretty-pbooleanfalse对生成的 SQL 做多行格式化方便阅读--dryrun-drbooleanfalse只把迁移内容打印到终端不写文件--check-chbooleanfalse校验数据库是否与实体一致CI 场景下非常有用--timestamp-tnumber当前时间为迁移文件指定自定义时间戳其中--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-refactoringTypeORM 会生成新文件{TIMESTAMP}-post-refactoring.ts内容大致如下import { MigrationInterface, QueryRunner } from typeorm export class PostRefactoringTIMESTAMP implements MigrationInterface { async up(queryRunner: QueryRunner): Promisevoid { await queryRunner.query( ALTER TABLE post ALTER COLUMN title RENAME TO name, ) } async down(queryRunner: QueryRunner): Promisevoid { 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): Promisevoid { // upSqls... } public async down(queryRunner: QueryRunner): Promisevoid { // 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/down、name属性保持一致。没有差异时怎么办退出码与空迁移兜底自动生成的前提是「实体与数据库之间存在差异」。如果你把实体改动同步进了数据库比如开启了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任何「实体已改但没补迁移」的提交都会让流水线失败从而守住「数据库变更必须有迁移文件」这条纪律。其他实用选项与推荐工作流--prettySQL 多行格式化默认情况下一句复杂的 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覆盖了postgres、mssql、mysql、mariadb、better-sqlite3、oracle、cockroachdb等主流数据库migration-generate.test.ts说明「实体 vs 数据库差值 → 生成迁移 SQL」的核心路径在不同方言上都被持续验证着。这也提醒我们虽然本文示例统一用了ALTER TABLE ... RENAME的写法但实际 SQL 必须由你连接的数据库驱动来决定。小结TypeORM 的migration:generate把「读实体、连数据库、算差值、写迁移」四步压缩成一条命令。核心要记住四点参数-d必须指向导出了 DataSource 实例的文件迁移名可以只写名字也可以带目录路径产物默认生成{TIMESTAMP}-{name}.ts含up/down及name属性全部 SQL 由createSchemaBuilder().log()自动对比产出纯 JS 项目-o输出.jsCommonJS叠加--esm切换为 ESM工程化用法--pretty提升可读性、--dryrun先预览、--check守护 CI、无差异时以退出码1明确拒绝生成并引导使用migration:create。关于迁移的完整工作流手动创建、执行、回滚、状态查看、假执行与 API 说明可以按顺序阅读 01-why、02-setup、03-creating、05-executing、06-reverting、07-status 与 09-api把自动生成真正融入你的数据库版本管理流程。【免费下载链接】typeormTypeScript JavaScript ORM for Node.js — supports PostgreSQL, MySQL, MariaDB, SQLite, SQL Server, Oracle, and more.项目地址: https://gitcode.com/GitHub_Trending/ty/typeorm创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考