- 后端
【免费下载链接】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.
本指南基于 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 → 转元数据 → 输出源码"三步:
- 通过
DatabaseSchema.create()读取数据库表结构(支持takeTables/skipTables过滤); - 将每个表转换为
EntityMetadata(实体元数据),并把外键等关系解析为对应属性; - 依据
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()实现,可以还原出一次完整生成的内部流程:
- 合并配置:
Utils.mergeConfig({}, config.get('entityGenerator'), options),调用参数覆盖全局配置; - 读取 Schema:
DatabaseSchema.create(connection, platform, config, ...),传入takeTables/skipTables过滤表;随后schema.loadRoutines()加载存储例程; - 生成元数据:
getEntityMetadata()中按options.schema过滤表、按skipColumns删除列(键使用table.getShortestName(false),即带 schema 前缀的表名),并为每个表调用getEntityDeclaration()产出实体元数据(EntityGenerator.ts); - 降级悬空外键:若某关系的目标表不在元数据集合中(被
skipTables/takeTables排除),该关系会被降级为ReferenceKind.SCALAR普通标量属性,仿佛外键不存在(EntityGenerator.ts)——这解释了文档中"如果外键引用了被跳过的表,生成的代码将如同该外键不存在"的行为; - 调用
onInitialMetadata钩子:在基类生成、M:N 检测、引用包装与双向关系检测之前处理元数据; - 处理类名冲突:不同 schema 下同名表生成同名的类时,会自动以
schema_className方式重命名并同步更新关系引用(EntityGenerator.ts); - 检测 M:N 关系:
detectManyToManyRelations()识别纯连接表(复合主键 + 恰好两个 M:1 主键关系),生成 M:N 属性; - 清理冗余引用完整性规则:FK 即主键、固定顺序连接表、指向复合主键的关系等默认规则不再显式输出(
cleanUpReferentialIntegrityRules); - 按需生成:
bidirectionalRelations(双向关系)、identifiedReferences(引用包装)、customBaseEntityName(自定义基类)、undefinedDefaults(null 默认值转 undefined)依次生效; - 调用
onProcessedMetadata钩子:在所有结构加工完成后、输出文件之前做最终调整; - 渲染并输出:按
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.
相关推荐
MikroORM Entity Generator 实战:从数据库 Schema 逆向生成 TypeScript 实体的完整指南
MikroORM Entity Generator 实战:从数据库 Schema 逆向生成 TypeScript 实体的完整指南 导读 本文围绕 MikroOR
后端MikroORM Schema First 完整实战:从既有数据库 Schema 反向生成可再生成实体并构建全栈应用
MikroORM Schema First 完整实战:从既有数据库 Schema 反向生成可再生成实体并构建全栈应用 本文基于 MikroORM 官方文档快照
后端MikroORM Schema Generator 完全指南:从实体元数据到数据库 Schema 的自动化管理
MikroORM Schema Generator 完全指南:从实体元数据到数据库 Schema 的自动化管理 导读 SchemaGenerator 是 Mik
后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考