拓冰建站拓冰建站
首页 / 资讯中心 / 正文

MikroORM 数据库迁移完全指南:从 Schema Diff 到生产环境发布

后端【免费下载链接】mikro-ormTypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, MariaDB, MS SQL Server, PostgreSQL and SQLite/libSQL databases.项目地址https://gitcode.com/gh_mirrors/mi/mikro-orm点击查看免费下载MikroORM 内置了基于 umzug 的数据库迁移Migrations能力可依据实体定义与数据库当前状态的差异自动生成迁移文件并通过事务化执行保障 schema 变更的一致性。本文以mikro-orm/migrations与mikro-orm/migrations-mongodb两个包为核心系统讲解迁移类编写、初始迁移、Schema 快照机制、全部配置项、CLI 与编程式 API、生产环境部署以及 MongoDB 场景下的差异并结合仓库源码packages/migrations/src揭示底层实现帮助你从生成到回滚完整驾驭 MikroORM 的迁移工作流。一、前置准备安装与扩展注册要启用迁移能力需要按数据库类型安装对应包并在 ORM 配置中注册Migrator扩展SQL 驱动MySQL、MariaDB、PostgreSQL、SQLite/libSQL、MSSQL 等使用mikro-orm/migrationsMongoDB 使用mikro-orm/migrations-mongodbimport { Migrator } from mikro-orm/migrations; // 或 mikro-orm/migrations-mongodb export default defineConfig({ // ... extensions: [Migrator], })从源码看Migrator.register()会将Migrator实例注册为mikro-orm/migrator扩展Migrator.ts随后即可通过orm.getMigrator()获取使用。Migrator继承自AbstractMigrator内部组合了三个核心组件MigrationRunner负责逐条执行迁移中的 SQLMigrationRunner.tsMigrationStorage负责在数据库中维护迁移执行记录表MigrationStorage.tsTSMigrationGenerator/JSMigrationGenerator负责生成迁移文件内容Migrator还持有SqlSchemaGenerator扩展用于生成 schema diff——这正是依据当前 schema 差异自动生成迁移的能力来源Migrator.ts。自 v5 起迁移文件存储时不带扩展名。默认情况下每个迁移会在独立事务中执行且所有迁移会被包裹在一个主事务master transaction中——只要其中一个失败整个批次全部回滚。二、编写迁移类Migration class迁移是一个继承Migration抽象类的类只需实现up()方法import { Migration } from mikro-orm/migrations; export class Migration20191019195930 extends Migration { async up(): Promisevoid { this.addSql(select 1 1); } }Migration基类Migration.ts提供了以下能力addSql()将一条 SQL 追加到当前迁移的查询队列中支持字符串 SQL、raw()SQL 片段甚至原生 Query Builder 实例NativeQueryBuilder也支持 knex 实例。MigrationGenerator.createStatement()会以模板字符串形式把语句包装成this.addSql(\...)MigrationGenerator.ts。execute()立即执行一条原始 SQL返回查询结果。它内部调用this.driver.execute(sql, params, all, this.ctx)因此查询会运行在与迁移相同的ctx事务上下文中。params参数仅在第一个参数为字符串 SQL 时生效。getKnex()获取当前驱动的 knex 实例方便构建更复杂的 SQL。getEntityManager()获取一个缓存、且已绑定当前迁移事务上下文的EntityManager实例。down()用于撤销迁移默认实现直接抛出This migration cannot be reverted错误只有显式实现down()的迁移才可回滚。isTransactional()返回布尔值控制当前迁移是否包裹在事务中默认true可按需重写。Configuration对象与 driver 实例通过构造函数注入分别以this.config与this.driver暴露可用于读取配置或直接调用驱动 API。在迁移中使用 EntityManager谨慎迁移的定位是修改 schema但你也可以用它修改数据——既可以通过this.execute()写原始 SQL也可以借助 EntityManagerimport { Migration } from mikro-orm/migrations; import { User } from ../entities/User; export class Migration20191019195930 extends Migration { async up(): Promisevoid { const em this.getEntityManager(); em.create(User, { ... }); await em.flush(); } }:::warning 在迁移中使用EntityManager虽然可行但不被推荐元数据会随应用演进而迁移执行时读取的是当前 checkout 的应用状态而非生成迁移时的状态容易产生偏差。迁移中应优先使用原始 SQL。 :::底层来看getEntityManager()通过driver.createEntityManager()创建并缓存一个 EM同时调用setTransactionContext(this.ctx)绑定事务Migration.ts确保 flush 出的写入与迁移 DDL 在同一事务内提交或回滚。三、初始迁移Initial migration当你已经拥有数据库 schema例如由schema:create或手工建表产生又希望引入迁移管理时可以创建初始迁移初始迁移仅当此前没有任何已生成或已执行的迁移时才能创建。npx mikro-orm migration:create --initial该命令生成的初始迁移包含完整的 schema 建表语句内部使用schemaGenerator.getCreateSchemaSQL()并且会自动将该迁移标记为已执行——因为目标 schema 已经存在于数据库中。源码中的validateInitialMigration()Migrator.ts对初始迁移的合法性做了三层校验如果已存在已执行或待执行的迁移直接抛出Initial migration cannot be created, as some migrations already exist如果没有实体定义抛出No entities found如果数据库中只存在部分实体对应的表抛出错误并列出已存在的表要求先清理后再创建初始迁移。只有当数据库表与元数据中的实体完全一致时迁移才会被自动标记为已执行。四、Schema 快照Snapshots每次创建新迁移时MikroORM 会把目标 schema即当前实体元数据对应的 schema自动保存为快照文件存放在迁移目录中。此后再次创建迁移时会优先基于快照进行 diff而不是实时读取数据库 schema。这意味着即使你还没有运行任何待执行的迁移生成的新迁移依然能得到正确的 schema 差异——新迁移基于上一次生成迁移时的目标状态做增量。快照应与普通迁移文件一样纳入版本控制。从源码实现看快照的保存与读取逻辑集中在 Migrator.ts快照文件名默认为.snapshot-dbName.json数据库名去掉路径分隔符与冒号存放在pathTsemit 为ts时或path目录中可通过snapshotName自定义快照文件名创建迁移时调用storeCurrentSchema()写入快照内容为JSON.stringify(schema, null, 2)生成 diff 时通过getSchemaFromSnapshot()读取快照并重建DatabaseSchema对象再交给SchemaComparator与目标 schema 对比。另外在up/down执行完迁移后如果配置了snapshotOnMigrateMigrator 还会从数据库反解当前 schema 并更新快照同时通过snapshotDiffers()比对语义差异避免无意义的cosmetic重写Migrator.ts。快照功能可通过migrations.snapshot: false关闭关闭后每次创建迁移都将直接对数据库实时 diff。五、完整配置Configuration自 v5 起使用 umzug 3.0原pattern选项已被glob取代。migrations.path与migrations.pathTs的解析方式与实体发现中的entities/entitiesTs完全一致。以下是全部默认值await MikroORM.init({ // default values: migrations: { tableName: mikro_orm_migrations, path: ./migrations, pathTs: undefined, glob: !(*.d).{js,ts,cjs}, silent: false, transactional: true, disableForeignKeys: false, allOrNothing: true, dropTables: true, safe: false, snapshot: true, emit: ts, generator: TSMigrationGenerator, fileName: (timestamp: string, name?: string) Migration${timestamp}${name ? _ name : }, }, })可用选项一览选项说明tableName: string存储迁移执行记录的数据库表名默认mikro_orm_migrations。也支持schema.table形式以指定 schema。path: string存放编译后迁移文件的目录默认./migrations。生产环境应指向 JS 文件所在目录。pathTs: string存放TypeScript 迁移源文件的目录开发阶段配合ts-node使用。若指定了它path应指向编译产物目录。glob: string匹配迁移文件的 glob 模式默认!(*.d).{js,ts,cjs}匹配所有 .js/.ts/.cjs 文件排除 .d.ts 声明文件。silent: boolean是否抑制迁移执行日志默认false。transactional: boolean是否将每条迁移包裹在事务中默认true。为false时迁移不会被自动包裹。disableForeignKeys: boolean迁移期间是否禁用外键检查默认false。为true时会在语句前后包裹set foreign_key_checks 0或驱动等价语句。allOrNothing: boolean是否将全部迁移包裹在一个主事务中默认true。任一迁移失败则整体回滚。dropTables: boolean是否允许迁移中删除表默认true。为false时跳过 DROP TABLE 操作。safe: boolean安全模式默认false。为true时同时禁止删表与删列用于高敏感环境。snapshot: boolean创建新迁移时是否保存 schema 快照默认true。快照用于迁移 diff应随迁移文件一起版本化。snapshotName: string快照文件自定义名称默认基于数据库名生成.snapshot-dbName.json。emit: js \| ts \| cjs生成迁移文件的格式默认ts。js为普通 JavaScriptcjs为 CommonJS 格式。generator: ConstructorIMigrationGenerator迁移文件内容生成器类默认TSMigrationGenerator对应 TS 输出可自定义以改变格式或结构。fileName: (timestamp: string, name?: string) string迁移文件命名函数接收时间戳与可选 name默认Migration${timestamp}${name ? _ name : }。migrationsList: (MigrationObject \| ConstructorMigration)[]直接以对象/类数组替代基于文件的迁移发现适用于 webpack 打包等文件系统访问受限的场景。自定义示例配置await MikroORM.init({ migrations: { tableName: my_migrations, path: dist/migrations, pathTs: src/migrations, glob: *.{js,ts}, silent: false, transactional: true, disableForeignKeys: true, allOrNothing: true, dropTables: false, // disable table dropping for safety safe: false, snapshot: true, emit: ts, fileName: (timestamp, name) ${timestamp}_${name || migration}, }, });环境变量覆盖上述选项也可以通过环境变量覆盖详见 配置文档MIKRO_ORM_MIGRATIONS_TABLE_NAMEMIKRO_ORM_MIGRATIONS_PATHMIKRO_ORM_MIGRATIONS_PATH_TSMIKRO_ORM_MIGRATIONS_GLOBMIKRO_ORM_MIGRATIONS_TRANSACTIONALMIKRO_ORM_MIGRATIONS_DISABLE_FOREIGN_KEYSMIKRO_ORM_MIGRATIONS_ALL_OR_NOTHINGMIKRO_ORM_MIGRATIONS_DROP_TABLESMIKRO_ORM_MIGRATIONS_SAFEMIKRO_ORM_MIGRATIONS_SILENTMIKRO_ORM_MIGRATIONS_EMITMIKRO_ORM_MIGRATIONS_SNAPSHOTMIKRO_ORM_MIGRATIONS_SNAPSHOT_NAME底层行为说明从源码层面理解这些选项的实际作用执行记录表MigrationStorage会按需创建记录表包含三列——自增主键id、迁移名name、执行时间executed_at默认当前时间戳并通过tableExists探测与自动建表MigrationStorage.ts。tableName支持schema.table形式此时表会被创建在指定 schema 下必要时自动创建 schema 命名空间。事务模型MigrationRunner.run()中当transactional为真且迁移的isTransactional()返回真时会通过connection.transactional()开启事务并传入{ ctx: this.#masterTransaction }把子事务挂到主事务下MigrationRunner.tsallOrNothing则决定是否建立这个主事务。外键处理disableForeignKeys为真时getQueries()会在迁移语句前后分别注入 schema 开头/结尾语句getSchemaBeginning/getSchemaEnd并在执行前后重置会话 schemaMigrationRunner.ts。生成流程Migrator.create()依次完成——读取快照hasSnapshot、计算 schema 差异getSchemaDiff、调用 generator 生成文件、保存新快照当 diff 为空时直接返回空结果而不产生文件Migrator.ts。diff 中的 SQL 会按语句边界分号加换行切分并兼容多行语句与字符串字面量Migrator.ts。文件命名MigrationGenerator.generate()以 ISO 时间戳去掉-、:、.与Z后作为迁移名前缀组合fileName回调与emit扩展名生成最终文件名写入baseDir解析后的目录并确保目录存在MigrationGenerator.ts。六、生产环境运行迁移Running migrations in production生产环境中通常希望执行编译后的 JS 迁移文件。自 v5 起这几乎开箱即用只需正确配置路径import { MikroORM, Utils } from mikro-orm/core; await MikroORM.init({ migrations: { path: dist/migrations, pathTs: src/migrations, }, // or alternatively // migrations: { // path: Utils.detectTsNode() ? src/migrations : dist/migrations, // }, // ... });这样CLI 环境通常启用了 TS 支持生成的是src/migrations下的 TS 迁移文件而生产环境未注册 ts-node则执行dist/migrations中编译后的 JS 文件。Utils.detectTsNode()可以在运行时自动探测当前是否运行在 ts-node 环境下从而动态切换路径。若要在生产/运行环境直接执行 TS 迁移文件请确保项目安装了ts-node——自 v6.3 起 CLI 会自动注册它。七、自定义 MigrationGenerator生成迁移文件内容的是MigrationGenerator类。你可以继承TSMigrationGenerator或JSMigrationGenerator提供自定义实现例如格式化 SQL 或追加注释import { TSMigrationGenerator } from mikro-orm/migrations; import { format } from sql-formatter; class CustomMigrationGenerator extends TSMigrationGenerator { generateMigrationFile(className: string, diff: { up: string[]; down: string[] }): string { const comment // this file was generated via custom migration generator\n\n; return comment super.generateMigrationFile(className, diff); } createStatement(sql: string, padLeft: number): string { sql format(sql, { language: postgresql }); // a bit of indenting magic sql sql.split(\n).map((l, i) i 0 ? l : ${ .repeat(padLeft 13)}${l}).join(\n); return super.createStatement(sql, padLeft); } } await MikroORM.init({ // ... migrations: { generator: CustomMigrationGenerator, }, });从源码看默认的TSMigrationGenerator会生成带override name与override up()/down()的 TS 类文件TSMigrationGenerator.ts而JSMigrationGenerator生成 CommonJS 风格的use strictrequire(mikro-orm/migrations)exports文件JSMigrationGenerator.ts。Migrator.getDefaultGenerator()会根据emit选项在二者间选择Migrator.ts。八、CLI 命令使用最常用的方式是通过 CLInpx mikro-orm migration:create # Create new migration with current schema diff npx mikro-orm migration:up # Migrate up to the latest version npx mikro-orm migration:down # Migrate one step down npx mikro-orm migration:list # List all executed migrations npx mikro-orm migration:check # Check if schema is up to date npx mikro-orm migration:pending # List all pending migrations npx mikro-orm migration:fresh # Drop the database and migrate up to the latest version创建空白迁移文件可使用npx mikro-orm migration:create --blank。migration:up与migration:down支持--from-f、--to-t和--only-o选项以执行迁移的子集npx mikro-orm migration:up --from 2019101911 --to 2019102117 # the same as above npx mikro-orm migration:up --only 2019101923 # apply a single migration npx mikro-orm migration:down --to 0 # migrate down all migrationsmigration:fresh支持--seed在重建后执行数据填充npx mikro-orm migration:fresh --seed # seed the database with the default database seeder npx mikro-orm migration:fresh --seedUsersSeeder # seed the database with the UsersSeeder默认 seeder 可在 ORM 配置中通过config.seeder.defaultSeeder指定。仓库中的 MigrationCommandFactory.ts 定义了完整的命令族除了上述命令还包含migration:log --name migration将某迁移标记为已执行不实际运行migration:unlog --name migration从执行记录中移除某迁移不撤销变更migration:rollup将多个迁移合并为一个其中migration:create还支持--dump将生成的 SQL 打印到控制台而不写文件、--path指定输出目录、--name自定义迁移名。另外CLI 在运行迁移命令时会特意将连接池最小/最大连接数设为 2pool: { min: 1, max: 2 }以支持主事务 记录表操作分离的双连接模型MigrationCommandFactory.ts。九、编程式使用 Migrator你也可以在自定义脚本中初始化 MikroORM 后直接调用 migrator APIimport { MikroORM } from mikro-orm/core; import { Migrator } from mikro-orm/migrations; (async () { const orm await MikroORM.init({ extensions: [Migrator], dbName: your-db-name, // ... }); const migrator orm.getMigrator(); await migrator.createMigration(); // creates file Migration20191019195930.ts await migrator.up(); // runs migrations up to the latest await migrator.up(name); // runs only given migration, up await migrator.up({ to: up-to-name }); // runs migrations up to given version await migrator.down(); // migrates one step down await migrator.down(name); // runs only given migration, down await migrator.down({ to: down-to-name }); // runs migrations down to given version await migrator.down({ to: 0 }); // migrates down to the first version await orm.close(true); })();随后用ts-node运行或编译为 JS 后用node运行$ ts-node migrate十、提供事务上下文某些场景下你可能希望自行控制事务上下文让迁移与业务逻辑在同一事务中执行await orm.em.transactional(async em { await migrator.up({ transaction: em.getTransactionContext() }); });这样migrator.up()内部的迁移查询就会挂载到外层em.transactional()开启的事务中实现业务操作 迁移的原子性。十一、静态导入迁移migrationsList当不希望动态加载目录例如用 webpack 打包应用时可以直接静态导入迁移类。可以使用显式迁移名或隐式以文件名作为迁移名import { MikroORM } from mikro-orm/core; import { Migrator } from mikro-orm/migrations; import { Migration20191019195930 } from ../migrations/Migration20191019195930.ts; import { Migration20191019195931 } from ../migrations/Migration20191019195931.ts; await MikroORM.init({ extensions: [Migrator], migrations: { migrationsList: [ // explicit migration name { name: CustomMigrationName, class: Migration20191019195930, }, // implicit migration name Migration20191019195931 ], }, });借助 webpack 的 context module API还可以动态收集整个目录下的迁移文件import { MikroORM } from mikro-orm/core; import { Migrator } from mikro-orm/migrations; import { basename } from path; const migrations {}; function importAll(r) { r.keys().forEach( (key) (migrations[basename(key)] Object.values(r(key))[0]) ); } importAll(require.context(../migrations, false, /\.ts$/)); const migrationsList Object.keys(migrations).map((migrationName) ({ name: migrationName, class: migrations[migrationName], })); await MikroORM.init({ extensions: [Migrator], migrations: { migrationsList, }, });从AbstractMigrator的源码可以确认当配置了migrationsList时Migrator 会跳过基于文件系统的迁移发现直接使用列表中的对象或类这在打包环境无文件系统访问下是必需的。十二、自定义迁移名称自 v5.7 起可通过--nameCLI 选项指定自定义迁移名它会追加到自动生成的时间戳前缀之后# generates file Migration20230421212713_add_email_property_to_user_table.ts npx mikro-orm migration:create --nameadd_email_property_to_user_table你也可以通过fileName回调自定义命名约定甚至强制要求必须提供名称migrations: { fileName: (timestamp: string, name?: string) { // force user to provide the name, otherwise you would end up with Migration20230421212713_undefined if (!name) { throw new Error(Specify migration name via mikro-orm migration:create --name...); } return Migration${timestamp}_${name}; }, },:::caution 重写migrations.fileName策略时务必保证生成的迁移文件名可排序——绝不能让自定义name成为文件名开头否则会导致迁移执行顺序错误。 :::十三、MongoDB 支持MongoDB 的迁移支持自 v5.3 引入使用独立包mikro-orm/migrations-mongodbCLI 命令体系完全兼容。在迁移中通过this.driver或this.getCollection()直接操作数据库import { Migration } from mikro-orm/migrations-mongodb; export class MigrationTest1 extends Migration { async up(): Promisevoid { // use this.getCollection() to work with the mongodb collection directly await this.getCollection(Book).updateMany({}, { $set: { updatedAt: new Date() } }, { session: this.ctx }); // or use this.driver to work with the MongoDriver API instead await this.driver.nativeDelete(Book, { foo: true }, { ctx: this.ctx }); } }MongoDB 下的事务注意事项Migrator默认启用事务而 MongoDB 的事务有一些额外要求集合需要预先存在且必须运行在replica set上。如果不符合条件可关闭事务migrations: { transactional: false }关闭事务后仍需手动为查询提供事务上下文——通过驱动方法的ctx选项或通过 MongoDB 的session选项await this.driver.nativeDelete(Book, { foo: true }, { ctx: this.ctx });await this.getCollection(Book).updateMany({}, { $set: { updatedAt: new Date() } }, { session: this.ctx });十四、已知限制LimitationsMySQLMySQL无法回滚 DDL 变更DDL 语句会自动触发隐式提交因此事务不会按预期工作。规划 MySQL 迁移时应避免依赖 DDL 回滚。MongoDB不支持嵌套事务不支持 schema diffing无法依据实体与数据库差异生成迁移只能生成空白迁移migration:create --blank风格的模板SQL 迁移的自动 diff 生成不适用于 MongoDB十五、调试Debuggingschema diff 偶尔会产生预期外的查询常见原因是属性的columnType或default/defaultRaw配置与数据库实际定义不一致。此时可用MIKRO_ORM_CLI_VERBOSE环境变量开启 CLI 的详细日志——它会输出用于提取当前 schema 的底层查询以及SchemaComparator中的比对日志帮助你定位 ORM 认为两列不同的具体差异点。排查迁移问题时直接使用schema:update会更高效——它跳过了 Migrator 层直接测试问题真正发生的 schema 比对层。$ MIKRO_ORM_CLI_VERBOSE1 npx mikro-orm schema:update --dump总结MikroORM 的迁移体系以schema 快照 差异生成 事务化执行为核心闭环migration:create依据快照与实体元数据生成增量迁移并更新快照migration:up/migration:down在主事务中逐个执行迁移并维护mikro_orm_migrations记录表。无论你是通过 CLI、编程式migratorAPI、还是打包场景下的migrationsList静态导入都可以在这套机制上获得一致的、可审计、可回滚的 schema 版本管理能力。结合 Migrator.ts、MigrationRunner.ts、MigrationStorage.ts 等源码深入理解其内部事务模型与快照策略将帮助你在生产环境的复杂 schema 演进中游刃有余。赞分享后端【免费下载链接】mikro-ormTypeScript ORM for Node.js based on Data Mapper, Unit of Work and Identity Map patterns. Supports MongoDB, MySQL, MariaDB, MS SQL Server, PostgreSQL and SQLite/libSQL databases.项目地址https://gitcode.com/gh_mirrors/mi/mikro-orm点击查看免费下载相关推荐Keystone 数据库迁移完全指南从开发期 db push 到生产环境 migrate deployKeystone 数据库迁移完全指南从开发期 db push 到生产环境 migrate deploy 本指南聚焦 Keystone基于 GraphQL 与后端探索现代UI组件库5个步骤打造专业级前端应用探索现代UI组件库5个步骤打造专业级前端应用 GitHub Trending UI组件库是一套精心设计、完全可访问的React组件集合专为现代前端开发而构建前端UI组件设计系统listmonk数据库迁移工具使用从开发到生产环境listmonk数据库迁移工具使用从开发到生产环境 listmonk作为高性能的自托管新闻通讯和邮件列表管理工具其数据库迁移功能是确保系统从开发环境平稳过渡后端企业应用上一篇react-big-calendar事件拖拽边界限制自定义范围与约束条件下一篇React Big Calendar终极定制指南深入事件渲染与TimeGrid组件扩展创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门