☰
MikroORM Entity Generator 完全指南:从已有数据库 Schema 反向生成 TypeScript 实体
2026/9/25 5:14:40 网站建设 项目流程
  • 后端

【免费下载链接】mikro-orm

TypeScript 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
点击查看免费下载

本指南基于 docs/versioned_docs/version-5.9/entity-generator.md 展开,并结合当前仓库(7.x)中@mikro-orm/entity-generator包的源码实现进行深度剖析。你将掌握两种调用方式(CLI 命令与初始化脚本)、全部高级配置选项的含义与优先级规则,以及生成器从"连接数据库 → 读取 Schema → 产出实体文件"的底层工作流程,从而在遗留数据库上快速建立与 MikroORM 对齐的实体层。

在采用 MikroORM 重构或接管一个已有数据库的项目时,最常见的诉求是:别让我手写几十上百个实体类。MikroORM 提供的EntityGenerator帮助类正是为此而生——它连接现有数据库,反向解析表结构、外键、枚举、存储例程等元数据,并自动生成对应的 TypeScript 实体源码。本文以 v5.9 版本文档为核心骨架,结合仓库当前实现源码(packages/entity-generator/src/EntityGenerator.ts)与类型定义(packages/core/src/typings.ts),完整讲解配置与实战细节。

一、EntityGenerator 是什么:从 Schema 到实体的逆向工程

EntityGenerator是一个基于已有数据库 Schema反向生成实体源码的辅助工具。在 v5.9 时代它隶属于核心包,而在当前 7.x 版本中,它被拆分到独立包@mikro-orm/entity-generator中(见 packages/entity-generator/package.json),并作为 ORM 扩展(extension)注册使用。

从源码看,生成器的核心工作并不神秘:EntityGenerator构造时从 EntityManager 上取得 driver、platform、schema helper 与连接(EntityGenerator.ts),随后在generate()方法中完成"读 Schema → 转元数据 → 输出源码"三步:

  1. 通过DatabaseSchema.create()读取数据库表结构(支持takeTables/skipTables过滤);
  2. 将每个表转换为EntityMetadata(实体元数据),并把外键等关系解析为对应属性;
  3. 依据entityDefinition选项选择SourceFile/DefineEntitySourceFile/EntitySchemaSourceFile三种渲染器,输出实体源码字符串,必要时写入磁盘。

值得强调的是,除了表实体,生成器还会为原生枚举(NativeEnumSourceFile)和存储例程(RoutineSourceFile,如函数、存储过程)生成对应源码,这在 v5.9 文档中虽未展开,但可从 EntityGenerator.ts 的循环中确认。

二、快速开始:CLI 一键生成

v5.9 文档给出的第一种用法是 CLI 命令。使用前需要先本地安装@mikro-orm/cli包,且其版本必须与@mikro-orm/core对齐。

npx mikro-orm generate-entities --dump # Dumps all generated entities npx mikro-orm generate-entities --save --path=./my-entities # Saves entities into given directory
  • --dump(别名-d):把生成的实体源码打印到控制台,适合快速预览;
  • --save(别名-s):把实体写入磁盘文件;
  • --path(别名-p):配合--save指定输出目录,即./my-entities;
  • --schema:只针对指定 schema 生成实体。

以上参数可从 CLI 命令实现 packages/cli/src/commands/GenerateEntitiesCommand.ts 中确认:若既不传--save也不传--dump,命令会直接展示帮助信息;成功保存后会输出Entities generated successfully。注意 CLI 初始化 ORM 时会强制设置discovery.warnWhenNoEntities = false,因为我们此时还没有任何实体,需要关闭"未发现实体"的告警。

v5.9 与当前版本的差异提示:v5.9 的 CLI 直接可用;而在当前 7.x 中,CLI 的generate-entities命令底层同样调用orm.entityGenerator.generate(...),且要求项目里已安装并注册@mikro-orm/entity-generator扩展(见下文"脚本方式")。

三、通过初始化脚本调用 EntityGenerator

CLI 之外,v5.9 文档还给出了脚本方式,适合把生成流程嵌入 CI、脚手架或需要编程控制选项的场景:

import { MikroORM } from '@mikro-orm/core'; (async () => { const orm = await MikroORM.init({ discovery: { // we need to disable validation for no entities warnWhenNoEntities: false, }, dbName: 'your-db-name', // ... }); const generator = orm.getEntityGenerator(); const dump = await generator.generate({ save: true, baseDir: process.cwd() + '/my-entities', }); console.log(dump); await orm.close(true); })();

随后通过ts-node运行(或先编译为纯 JS 再用node执行):

$ ts-node generate-entities

关键点拆解:

  • warnWhenNoEntities: false:初始化时项目里还没有任何实体,必须关闭无实体告警,否则 ORM 启动会报错;
  • orm.getEntityGenerator():v5.9 的 API 入口;
  • generate({ save: true, baseDir: ... }):save: true表示写盘,baseDir指定输出目录;
  • await orm.close(true):任务结束后显式关闭连接,true表示强制关闭;
  • 返回值dump是字符串数组,每个元素对应一个生成文件的源码内容,可直接console.log或继续二次加工。

当前版本迁移提示:在 7.x 中,getEntityGenerator()已改为通过扩展注册后的orm.entityGenerator,且baseDir选项更名为path。当前GenerateOptions类型定义见 packages/core/src/typings.ts,注册方式为在配置中声明extensions: [EntityGenerator]:

import { defineConfig } from '@mikro-orm/postgresql'; import { EntityGenerator } from '@mikro-orm/entity-generator'; export default defineConfig({ dbName: 'test', extensions: [EntityGenerator], });

对应脚本调用为:

const dump = await orm.entityGenerator.generate({ save: true, path: process.cwd() + '/my-entities', });

四、高级配置选项详解

v5.9 文档指出:默认情况下,EntityGenerator只生成关系中的 owning side(拥有方,如 M:1),并使用装饰器定义实体。行为可通过 ORM 配置中的entityGenerator段调整。文档列出的选项如下:

  • bidirectionalRelations:同时生成关系的 inverse side(被拥有方/反向方);
  • identifiedReferences:将 M:1 与 1:1 关系生成为 wrapped references(引用包装);
  • entitySchema:改用EntitySchema而非装饰器定义实体;
  • esmImport:使用 ESM 风格导入,例如esmImport=true时生成import Author from './Author.js';
  • skipTables:忽略指定数据库表(接受表名数组);
  • skipColumns:忽略指定表的某些列(接受对象,键为带 schema 前缀(如有)的表名,值为列名数组)。

示例:

const dump = await orm.entityGenerator.generate({ save: true, baseDir: process.cwd() + '/my-entities', skipTables: ['book', 'author'], skipColumns: { 'public.user': ['email', 'middle_name'], }, });

选项优先级:generate 参数 > 全局 entityGenerator 配置

在当前源码实现中,generate(options)的第一步就是合并配置:

options = Utils.mergeConfig({}, this.#config.get('entityGenerator'), options);

(见 EntityGenerator.ts)

这意味着你既可以在 ORM 配置的entityGenerator段设置全局默认行为,也可以在每次调用generate()时传参覆盖——调用时传入的选项拥有更高优先级。这与新版文档"GenerateOptions对象优先于全局配置"的描述一致。

完整选项清单(当前版本,含 v5.9 之外的新增项)

除了 v5.9 文档中的六项,当前仓库的GenerateOptions还提供了更多精细控制,可参看 packages/core/src/typings.ts:

选项说明
path/save输出目录(默认baseDir/generated-entities)与是否写盘
schema只对指定 schema 生成实体
takeTables只考虑指定表,接受字符串或RegExp;被引用但未包含的表其外键会被当作不存在
skipTables/skipColumns忽略表/列;同样支持RegExp
forceUndefined可空属性按"不存在null值"处理,类型提示中省略null
undefinedDefaults把数据库上报的null默认值转为undefined,属性变为可选
entityDefinition实体定义输出方式:'decorators'|'defineEntity'|'entitySchema',默认'decorators'
inferEntityType与defineEntity搭配,仅输出类型(基于InferEntity)而非类声明
enumMode枚举输出方式:'ts-enum'(默认)|'union-type'|'dictionary'
scalarTypeInDecorator在标量属性装饰器中直接写入type选项,省去运行时发现
scalarPropertiesForRelations外键列是否生成标量属性:'never'(默认)|'always'|'smart'
onlyPurePivotTables/outputPurePivotTables/readOnlyPivotTables控制 M:N 连接表的生成策略(见下文)
customBaseEntityName/useCoreBaseEntity为实体附加自定义基类,或使用@mikro-orm/core的BaseEntity
coreImportsPrefix为来自 MikroORM 核心的导入添加别名前缀,规避与表名/类型名冲突
fileName/onImport/extraImports文件命名回调、导入解析回调与附加导入回调
onInitialMetadata/onProcessedMetadata元数据处理钩子(见下文"元数据加工")

一个较为完整的组合示例(来自新版文档,展示了如何同时启用多项):

const dump = await orm.entityGenerator.generate({ entitySchema: true, bidirectionalRelations: true, identifiedReferences: true, esmImport: true, save: true, path: process.cwd() + '/my-entities', skipTables: ['book', 'author'], skipColumns: { 'public.user': ['email', 'middle_name'], }, });

五、源码级原理:generate() 的完整工作流程

阅读 EntityGenerator.ts 的generate()实现,可以还原出一次完整生成的内部流程:

  1. 合并配置:Utils.mergeConfig({}, config.get('entityGenerator'), options),调用参数覆盖全局配置;
  2. 读取 Schema:DatabaseSchema.create(connection, platform, config, ...),传入takeTables/skipTables过滤表;随后schema.loadRoutines()加载存储例程;
  3. 生成元数据:getEntityMetadata()中按options.schema过滤表、按skipColumns删除列(键使用table.getShortestName(false),即带 schema 前缀的表名),并为每个表调用getEntityDeclaration()产出实体元数据(EntityGenerator.ts);
  4. 降级悬空外键:若某关系的目标表不在元数据集合中(被skipTables/takeTables排除),该关系会被降级为ReferenceKind.SCALAR普通标量属性,仿佛外键不存在(EntityGenerator.ts)——这解释了文档中"如果外键引用了被跳过的表,生成的代码将如同该外键不存在"的行为;
  5. 调用onInitialMetadata钩子:在基类生成、M:N 检测、引用包装与双向关系检测之前处理元数据;
  6. 处理类名冲突:不同 schema 下同名表生成同名的类时,会自动以schema_className方式重命名并同步更新关系引用(EntityGenerator.ts);
  7. 检测 M:N 关系:detectManyToManyRelations()识别纯连接表(复合主键 + 恰好两个 M:1 主键关系),生成 M:N 属性;
  8. 清理冗余引用完整性规则:FK 即主键、固定顺序连接表、指向复合主键的关系等默认规则不再显式输出(cleanUpReferentialIntegrityRules);
  9. 按需生成:bidirectionalRelations(双向关系)、identifiedReferences(引用包装)、customBaseEntityName(自定义基类)、undefinedDefaults(null 默认值转 undefined)依次生效;
  10. 调用onProcessedMetadata钩子:在所有结构加工完成后、输出文件之前做最终调整;
  11. 渲染并输出:按entityDefinition选择渲染器,默认输出目录为${baseDir}/generated-entities(EntityGenerator.ts)。

输出文件的安全边界

一个值得注意的安全细节:写盘前,生成器会校验所有目标文件名必须落在项目目录或指定输出目录内,任何试图逃逸到任意文件系统位置的文件名都会抛出Cannot generate '...', it resolves outside of the project folder错误(EntityGenerator.ts)。这保证了基于数据库表名拼接的文件路径不会被恶意利用。

关系生成的三种核心策略

  • 默认(owning side only):只生成关系的拥有方属性(如 M:1 的外键引用);
  • bidirectionalRelations: true:为每个关系补充 inverse side,M:1 生成 1:M,1:1 与 M:N 生成对应反向属性,反向属性名由inverseSideName()推导,若与已有属性冲突会自动追加数字后缀(EntityGenerator.ts);
  • identifiedReferences: true:将 M:1、1:1 关系(以及所有lazy属性)标记为ref: true,输出为Reference包装引用(EntityGenerator.ts)。

M:N 连接表的智能识别

detectManyToManyRelations()(EntityGenerator.ts)对连接表的判定条件相当严格:非复合主键的表永远不可能是连接表;只有"复合主键 + 恰好两个均为 M:1 的主键关系"的表才进入候选。此外:

  • 连接表若含额外列(如created_at),默认仍生成 M:N(除非onlyPurePivotTables: true);
  • 额外列中有非空且唯一的列、或不可选属性时,集合被视为只读,仅在readOnlyPivotTables: true时生成,并附带persist: false;
  • 纯连接表默认不单独输出为实体文件(除非被外键引用或outputPurePivotTables: true);
  • 连接表存在自增主键时,会被识别为"固定顺序列"(fixedOrder),生成带固定顺序的 M:N 集合。

六、对生成元数据的二次加工:onInitialMetadata / onProcessedMetadata

数据库 Schema 无法表达所有业务信息,因此 v5.9 之后版本提供了两个元数据处理钩子,在写入文件前修改生成的实体元数据(注意:这与配置中的onMetadata钩子不同——onMetadata不影响实体文件,而生成器选项里的这两个钩子会直接影响输出)。适合在钩子里完成的操作包括:

  • 为特定列/关系添加hidden标记(序列化时隐藏);
  • 添加序列化groups、lazy、eager、mapToPk、orphanRemoval、cascade等选项;
  • 调整标量属性的type/runtimeType为自定义类型;
  • 添加单表继承(STI)、@Embedded内嵌实体、virtual 实体以及带formula的属性。

例如,让所有名为password的列变为懒加载且隐藏,并让所有 M:N 关系在序列化时隐藏:

import { ReferenceKind, MikroORM } from '@mikro-orm/core'; const orm = await MikroORM.init({ // ORM config }); await orm.entityGenerator.generate({ onInitialMetadata: (metadata, platform) => { metadata.forEach(meta => { meta.props.forEach(prop => { if (prop.name === 'password') { prop.hidden = true; prop.lazy = true; } }); }); }, onProcessedMetadata: (metadata, platform) => { metadata.forEach(meta => { meta.props.forEach(prop => { if (prop.kind === ReferenceKind.MANY_TO_MANY) { prop.hidden = true; } }); }); }, });

钩子同样适合把 JSON 列改造为@Embedded内嵌实体,或在脚本中直接new EntityMetadata(...)创建 embeddable 元数据并推入集合;生成器会据此在实体中输出@Embedded({ entity: () => IdentitiesContainer, array: true, object: true, prefix: false, nullable: true })之类的引用。由于元数据对象是内部结构,修改时的校验较少,出错信息可能不够直观,建议在钩子中保持谨慎。

七、当前限制与注意事项

v5.9 文档明确指出的限制是:

  • MySQL 下tinyint列会被定义为 boolean 属性:MySQL 没有真正的BOOLEAN类型(该关键字只是TINYINT(1)的别名),因此生成器只能将其映射为 boolean。

当前版本文档在此基础上有两条补充:

  • MongoDB 不被支持:EntityGenerator 面向关系型数据库的 Schema 反向工程,文档型数据库不在其列;
  • 生成实体默认不继承任何基类;如需统一基类,可使用customBaseEntityName(自动创建同名基类并让所有无继承的实体继承它)或useCoreBaseEntity(继承@mikro-orm/core的BaseEntity)。

此外还有两条实用建议:esmImport: true会在导入语句中附加.js后缀以适配 ESM 运行时;同时,在不使用引用包装时,生成器会为所有 M:1、1:1 关系加上Rel<>包装,规避循环引用导致的Cannot access 'X' before initialization类初始化顺序错误——这是当前版本新增的防坑机制。

八、从测试看行为契约

仓库中的测试可以作为上述行为的可验证依据:

  • tests/features/entity-generator/EntityGenerator.postgres.test.ts 验证了skipTables: ['test2', 'test2_bars']后,Test2.ts不再生成,而Author2.ts、FooBar2.ts正常输出,并断言Author2.ts存在、Test2.ts不存在;另有takeTables与skipTables配合RegExp(如/^foo_bar\d$/)的测试;
  • tests/features/entity-generator/EntityGenerator.mysql.test.ts 以describe.each([true, false])组合遍历bidirectionalRelations与identifiedReferences的四种开关组合,确认不同配置下的快照输出符合预期;
  • tests/features/entity-generator/MetadataHooks.mysql.test.ts 覆盖了onInitialMetadata/onProcessedMetadata钩子的实际效果。

结语

从 v5.9 到当前 7.x,EntityGenerator 的核心理念始终如一:读取数据库 Schema,输出与 MikroORM 对齐的实体源码。默认生成 owning side、装饰器风格实体;通过entityGenerator配置或generate()参数,可以自由切换到双向关系、引用包装、EntitySchema/defineEntity定义方式、表列过滤、自定义类型与元数据钩子等高级模式。对遗留数据库接入 MikroORM 的场景而言,这条"Schema → 实体"的逆向通道能显著降低手工建模成本,而源码中的过滤降级、连接表识别与路径安全检查等细节,则保证了生成结果在生产环境中的可用性与安全性。

  • 后端

【免费下载链接】mikro-orm

TypeScript 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
点击查看免费下载
上一篇:探索色彩的无限可能 —— Tint & Shade Generator项目评测
下一篇:OneDiff 开源项目教程

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询