尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

MikroORM v5 装饰器完整指南:从 @Entity 到 @Subscriber 的实体定义与生命周期钩子详解

发布时间:2026/9/25 5:44:01

资讯中心
01
ARTICLE

MikroORM v5 装饰器完整指南:从 @Entity 到 @Subscriber 的实体定义与生命周期钩子详解

MikroORM v5 装饰器完整指南:从 @Entity 到 @Subscriber 的实体定义与生命周期钩子详解
后端【免费下载链接】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 5.x 官方文档的装饰器参考docs/versioned_docs/version-5.9/decorators.md为骨架结合仓库中mikro-orm/decorators包的真实源码实现系统讲解实体类装饰器、属性装饰器、关系装饰器、生命周期钩子与事件订阅的全部参数和用法。读完本文你将能熟练使用Entity、Property、PrimaryKey、Enum、Formula、Index/Unique、Check以及四大关系装饰器完成实体建模并用生命周期钩子与Subscriber精确控制实体在增删改查过程中的行为。MikroORM 是一个基于 Data Mapper、Unit of Work 与 Identity Map 模式的 TypeScript ORM支持 MongoDB、MySQL、MariaDB、MS SQL Server、PostgreSQL 与 SQLite/libSQL。它的实体可以通过两种方式定义装饰器类与EntitySchema辅助类见 Defining Entities。本文聚焦装饰器方式——在类上使用Entity()在每个属性上使用Property或关系装饰器由元数据提供器Metadata Provider负责把装饰器收集到的信息转成实体元数据最终驱动 Schema Generator、查询构建、序列化与变更检测等全部能力。一、装饰器在 MikroORM 中的角色与两种元数据提供器装饰器本身只是元数据收集器它们把配置写入类与属性的元数据对象真正消费这些元数据的是一套独立的元数据系统。从源码看mikro-orm/decorators包提供了两套实现目录结构为packages/decorators/src/legacy/传统 TypeScript 装饰器tsc默认experimentalDecorators模式packages/decorators/src/es/TC39 Stage-3 原生装饰器使用ClassDecoratorContext、ClassFieldDecoratorContext等标准 API。两套实现最终都依赖packages/decorators/src/utils.ts中的getMetadataFromDecorator()它会通过MetadataStorage.getMetadata()按实体类名获取/创建元数据对象并通过lookupPathFromDecorator()分析调用栈定位装饰器所在的源码文件路径该函数兼容__decorate、__esDecorate、Reflect.decorate、_applyDecoratedDescriptor等多种装饰器编译产物见 packages/decorators/src/utils.ts。装饰器能省略多少参数取决于你配置的元数据提供器详见 Metadata ProvidersReflectMetadataProvider基于reflect-metadata读取属性类型更快但更啰嗦——关系中必须显式给出entityTsMorphMetadataProvider基于ts-morph从 TypeScript 编译 API 读取类型信息能自动嗅探属性类型包括接口名与可选性实现 DRY 的实体定义但依赖完整 TS 编译环境。一个典型的实体定义如下摘自 defining-entities.mdEntity() export class Book extends CustomBaseEntity { Property() title!: string; ManyToOne(() Author) author!: Author; ManyToOne(() Publisher, { ref: true, nullable: true }) publisher?: RefPublisher; ManyToMany({ entity: BookTag, fixedOrder: true }) tags new CollectionBookTag(this); }注意MikroORM 的装饰器设计上以tsc编译为准使用babel/swc需要额外配置见 usage-with-transpilers而ts-morph只兼容tsc方案。二、实体定义Entity()Entity()用于把模型类标记为实体。不要把它用在抽象基类上——抽象基类应由子类继承而非直接注册。参数类型可选说明tableNamestring是覆盖默认的集合/表名schemastring是设置 schema 名称collectionstring是tableName的别名commentstring是表的注释仅 SQLcustomRepository() EntityRepository是设置自定义仓库类见 repositories.mddiscriminatorColumnstring是用于单表继承见 inheritance-mapping.mddiscriminatorMapDictionarystring是用于单表继承discriminatorValuenumber \| string是用于单表继承forceConstructorboolean是创建受管实体实例时强制调用构造函数abstractboolean是标记实体为抽象发现discovery阶段会被内联readonlyboolean是禁用变更追踪flush 时忽略该实体Entity({ tableName: authors }) export class Author { ... }从 packages/decorators/src/legacy/Entity.ts 可以看到实现细节Entity通过Utils.mergeConfig(meta, options)把选项合并进元数据写入meta.class当options.abstract为真且没有discriminatorColumn/discriminator时不会设置meta.name这正是抽象实体在发现阶段被内联、不单独注册的机制来源。TC39 版本的实现packages/decorators/src/es/Entity.ts则通过context.metadata读取标准装饰器上下文元数据。说明tableName/schema/comment等选项影响 Schema Generator 的行为这些是SQL 专用选项——MongoDB 没有 schema 概念因此这些选项对 Mongo 驱动不生效。三、实体属性Property()Property()用于定义普通实体属性。以下所有装饰器都继承自Property()因此你在Property()中看到的参数同样可用于关系、主键、枚举等装饰器。参数类型可选说明fieldNamestring是覆盖默认属性名见 Naming Strategytypestring \| ConstructorType \| Type是显式指定运行时类型见 Metadata Providers 与 Custom TypescustomTypeType是为该属性显式指定映射类型实例见 Custom Typesreturningboolean是该属性是否应出现在returning子句中仅 PostgreSQL 与 SQLite 驱动支持onUpdate() any是实体每次更新时自动刷新该属性值persistboolean是设为false定义影子属性见 serializing.mdhydrateboolean是设为false禁用该属性水合对持久化 getter 很有用hiddenboolean是设为true在序列化时省略该属性见 serializing.mdcolumnTypestring是为 Schema Generator 指定精确的数据库列类型仅 SQLlengthnumber是数据库列的长度/精度用于datetime/timestamp/varchar列类型仅 SQLdefaultany是指定列的默认值仅 SQLuniqueboolean是列为唯一仅 SQLnullableboolean是列可空仅 SQLunsignedboolean是列为无符号仅 SQLcommentstring是列注释仅 SQLversionboolean是设为true通过版本字段启用乐观锁见 transactions.md仅 SQLconcurrencyCheckboolean是设为true通过并发字段启用并发检查见 transactions.mdcustomOrderstring[] \| number[] \| boolean[]是指定该列的自定义排序仅 SQL属性的初始化器可以照常使用Property({ length: 50, fieldName: first_name }) name!: string; Property({ columnType: datetime, fieldName: born_date }) born?: Date; Property({ columnType: tinyint }) age?: number; Property({ onUpdate: () new Date() }) updatedAt new Date(); Property() registered false;源码层面packages/decorators/src/legacy/Property.ts有几个值得注意的机制name与fieldName的自动转换若传入的name与真实属性名不同装饰器会把name重命名为fieldName从而让属性名 vs 列名分离getter/setter 识别通过Object.getOwnPropertyDescriptor检测desc.get/desc.set自动设置prop.getter/prop.setter方法即计算属性当被装饰的值是Function时属性被标记为getter true、persist false、type method这正是持久化 getter的底层实现check 快捷选项Property({ check })会直接向meta.checks数组推入检查约束见下文Check()TC39 版本packages/decorators/src/es/Property.ts则按context.kind区分field、getter、setter、accessor、method五种上下文分别设置getter/setter/persist等标志。四、主键PrimaryKey() 与 SerializedPrimaryKey()PrimaryKey()定义实体的唯一主键标识。它同样继承Property()的全部参数。每条实体至少需要一个主键复合主键参见 composite-keys.md。若只设置一个PrimaryKey()且类型为number在所有 SQL 驱动中会自动设为自增auto increment。PrimaryKey() id!: number; // SQL 驱动中的自增主键 PrimaryKey({ autoincrement: false }) id!: number; // 不自增的数字主键 PrimaryKey() uuid: string uuid.v4(); // SQL 驱动中的 uuid 主键 PrimaryKey() _id!: ObjectId; // MongoDB 驱动中的 ObjectId 主键SerializedPrimaryKey()标记的属性是虚拟的不会持久化到数据库。MongoDB 场景下实体序列化JSON.stringify()经由entity.toJSON()时使用序列化主键从而让你能以字符串形式操作 ObjectId。典型搭配PrimaryKey() _id: ObjectId; SerializedPrimaryKey() id!: string;实现上packages/decorators/src/legacy/PrimaryKey.ts两个装饰器共用同一个createDecorator工厂区别仅在于向属性元数据写入primary: true还是serializedPrimaryKey: true同时通过validateSingleDecorator校验该属性此前未被其他属性装饰器标记。相关用法还可参考 usage-with-mongo.md 与 serializing.md。五、枚举属性Enum()Enum()同时支持数字枚举与字符串枚举。默认情况下枚举被视为数字类型在数据库 schema 中表现为tinyint/smallint字符串枚举如果定义在同一个文件里其值会被自动嗅探见 defining-entities.md。参数类型可选说明itemsnumber[] \| string[] \| () Dictionary是显式指定枚举项Enum() // 使用 ts-morph 元数据提供器时无需指定任何参数 enum0 MyEnum1.VALUE_1; Enum(() MyEnum1) // 或 Enum({ items: () MyEnum1 }) enum1 MyEnum1.VALUE_1; Enum({ type: MyEnum2, nullable: true }) enum2?: MyEnum2; // MyEnum2 需在当前文件中定义可再导出 Enum({ items: [1, 2, 3] }) enum3 3; Enum({ items: [a, b, c] }) enum4 a;从 packages/decorators/src/legacy/Enum.ts 可以看到当第一个参数是函数时装饰器自动把它包装为{ items: options }并在属性元数据上写入kind: ReferenceKind.SCALAR、enum: true。这也解释了为什么Enum(() MyEnum1)与Enum({ items: () MyEnum1 })完全等价。六、计算列Formula()Formula()把一段 SQL 片段映射到实体属性上。SQL 片段可以任意复杂甚至包含子查询见 defining-entities.md。参数类型可选说明formulastring \| () string否将进入 select 子句的 SQL 片段Formula(obj_length * obj_height * obj_width) objectVolume?: number;七、索引与唯一约束Index() 与 Unique()Index()创建索引Unique()创建唯一约束。两者既可用于实体类级别也可用于属性级别。创建复合索引时把装饰器放在实体类级别并通过properties选项给出属性名列表见 defining-entities.md。参数类型可选说明namestring是索引名propertiesstring \| string[]是属性名列表实体级别使用时必填typestring是索引类型Unique()不可用。传fulltext可启用$fulltext操作符支持Entity() Index({ properties: [name, age] }) // 复合索引名称自动生成 Index({ name: custom_idx_name, properties: [name] }) // 简单索引自定义名称 Unique({ properties: [name, email] }) export class Author { Property() Unique() email!: string; Index() // 名称自动生成 Property() age?: number; Index({ name: born_index }) Property() born?: Date; }源码packages/decorators/src/legacy/Indexed.ts中两者共用一个工厂Index()把选项推入meta.indexesUnique()推入meta.uniques当装饰器用在属性上时options.properties ?? propertyName会把当前属性名自动填入properties因此属性级别的Index()无需任何参数即可生效。八、检查约束Check()Check()用于定义检查约束既可用于实体类也可用于实体属性。必填的expression可以是字符串也可以是回调——回调会收到一份属性名 → 列名的映射。若想获得列名的 TypeScript 智能提示请使用泛型参数CheckBook。检查约束目前仅在 PostgreSQL 驱动中受支持。参数类型可选说明namestring是约束名propertystring是属性名仅用于生成约束名expressionstring \| CheckCallback否约束定义可为接收属性到列名映射的回调Entity() // 基于表名自动生成约束名 Check({ expression: price1 0 }) // 显式约束名 Check({ name: foo, expression: columns ${columns.price1} 0 }) // 显式泛型参数columns 获得自动补全 CheckBook({ expression: columns ${columns.price1} 0 }) export class Book { PrimaryKey() id!: number; Property() price1!: number; Property() Check({ expression: price2 0 }) price2!: number; Property({ check: columns ${columns.price3} 0 }) price3!: number; }注意最后一个示例Property({ check })是Check()的等价内联写法Property装饰器会把check选项转成{ property: prop.name, expression: check }推入meta.checks见 packages/decorators/src/legacy/Property.ts。九、关系装饰器的通用要点所有关系装饰器ManyToOne、OneToOne、OneToMany、ManyToMany都有可选的entity、cascade、eager参数使用默认的ReflectMetadataProvider时entity参数可能必填若缺失discovery 过程中会收到警告也可以用type参数代替entity区别在于type必须是字符串而entity可以传入实体引用用回调包裹以解决循环依赖问题并且能很好地配合 WebStorm 等 IDE 的重构功能若显式以引用形式提供entity会为inversedBy/mappedBy等其他引用参数开启类型检查。此外所有关系装饰器都继承Property()的参数如nullable、fieldName等。参数处理逻辑集中在 packages/decorators/src/utils.ts 的processDecoratorParameters()中它允许第一种参数即选项对象和全部位置参数两种签名若把两者混用如第一个参数传了对象、后面又传位置参数会抛出 Mixing first decorator parameter as options object with other parameters is forbidden 错误。同一属性也不允许叠加多个不同类型的属性装饰器validateSingleDecorator例如Property()与ManyToOne()不能同时使用。9.1 ManyToOne()当前实体的多个实例指向被引用实体的一个实例见 relationships.md。参数类型可选说明entitystring \| () EntityName是目标实体类型cascadeCascade[]是拥有方实体的哪些操作级联到该关系默认[Cascade.PERSIST, Cascade.MERGE]见 cascading.mdeagerboolean是总是加载该关系inversedBy(string keyof T) \| (e: T) any是指向反向侧属性名wrappedReferenceboolean是将实体包裹进Reference包装器见 type-safe-relations.mdrefboolean是wrappedReference的别名primaryboolean是将该关系用作主键onDeletestring是声明式引用完整性见 cascading.mdonUpdateIntegritystring是声明式引用完整性ManyToOne() author1?: Author; // 类型通过反射获取TsMorphMetadataProvider ManyToOne(() Author) // 显式类型 author2?: Author; ManyToOne({ entity: () Author, cascade: [Cascade.ALL] }) // 选项对象 author3?: Author;从 packages/decorators/src/legacy/ManyToOne.ts 可以看到ManyToOne声明了三种重载签名实体回调 选项、字符串实体、纯选项对象最终都归一化为向meta.properties写入kind: ReferenceKind.MANY_TO_ONE的属性定义并与已有的属性元数据做Object.assign合并。9.2 OneToOne()当前实体的一个实例指向被引用实体的一个实例双向 1:1 示例见 relationships.md。参数类型可选说明entitystring \| () EntityName是目标实体类型cascadeCascade[]是级联操作默认[Cascade.PERSIST, Cascade.MERGE]eagerboolean是总是加载该关系ownerboolean是显式设为拥有侧等价于提供inversedByinversedBy(string keyof T) \| (e: T) any是指向反向侧属性名mappedBy(string keyof T) \| (e: T) any是指向拥有侧属性名wrappedReferenceboolean是包裹进Reference包装器refboolean是wrappedReference别名orphanRemovalboolean是实体从关系中脱离时删除它见 cascading.mdjoinColumnstring是覆盖拥有侧默认数据库列名见 Naming Strategyprimaryboolean是将该关系用作主键onDeletestring是声明式引用完整性onUpdateIntegritystring是声明式引用完整性// 未提供 owner/inverseBy/mappedBy 时该侧被当作拥有侧 OneToOne() bestFriend1!: User; // 带 inversedBy 的一侧是拥有侧定义反向侧用 mappedBy OneToOne({ inversedBy: bestFriend1, orphanRemoval: true }) bestFriend2!: User; // 用这种写法时必须用 owner: true 显式标记拥有侧 OneToOne(() User, user user.bestFriend2, { owner: true, orphanRemoval: true }) bestFriend3!: User;9.3 OneToMany()当前实体的一个实例拥有被引用实体的多个实例双向 1:m 示例见 relationships.md。必须用CollectionT实例初始化该属性的值。参数类型可选说明mappedBy(string keyof T) \| (e: T) any否指向拥有侧属性名entitystring \| () EntityName是目标实体类型cascadeCascade[]是级联操作默认[Cascade.PERSIST, Cascade.MERGE]eagerboolean是总是加载该关系orphanRemovalboolean是实体从关系中脱离时删除它见 cascading.mdorderBy{ [field: string]: QueryOrder }是设置默认排序条件joinColumnstring是覆盖拥有侧默认数据库列名inverseJoinColumnstring是覆盖反向侧默认数据库列名OneToMany(() Book, book book.author) books1 new CollectionBook(this); OneToMany({ mappedBy: author, cascade: [Cascade.ALL] }) books2 new CollectionBook(this); // 目标实体类型同样可由 TsMorphMetadataProvider 读取实现上packages/decorators/src/legacy/OneToMany.tsOneToMany同样支持回调 mappedBy 选项与纯选项对象两种签名写入的元数据为kind: ReferenceKind.ONE_TO_MANY。CollectionT的详细语义见 collections.md。9.4 ManyToMany()当前实体的多个实例指向被引用实体的多个实例双向 m:n 示例见 relationships.md。必须用CollectionT实例初始化该属性的值。参数类型可选说明entitystring \| () EntityName是目标实体类型cascadeCascade[]是级联操作默认[Cascade.PERSIST, Cascade.MERGE]eagerboolean是总是加载该关系ownerboolean是显式设为拥有侧等价于提供inversedByinversedBy(string keyof T) \| (e: T) any是指向反向侧属性名mappedBy(string keyof T) \| (e: T) any是指向拥有侧属性名orderBy{ [field: string]: QueryOrder }是设置默认排序条件fixedOrderboolean是强制保持集合内条目稳定的插入顺序见 collections.mdfixedOrderColumnstring是覆盖默认顺序列名默认idpivotTablestring是覆盖默认中间表名见 Naming StrategyjoinColumnstring是覆盖拥有侧默认数据库列名inverseJoinColumnstring是覆盖反向侧默认数据库列名ManyToMany({ entity: () BookTag, cascade: [], fixedOrderColumn: order }) tags new CollectionBookTag(this); // 自增主键的 m:n ManyToMany(() BookTag, undefined, { pivotTable: book_to_tag_unordered, orderBy: { name: QueryOrder.ASC } }) tagsUnordered new CollectionBookTag(this); // 复合主键的 m:n十、生命周期钩子在持久化过程中挂接业务逻辑生命周期钩子用于在实体被持久化时执行自定义代码。任意实体方法都可以标记为钩子同一个钩子可以标记多个方法。所有钩子都支持 async 方法唯一例外是OnInit。在源码中钩子通过统一的hook(type)工厂实现见 packages/decorators/src/legacy/hooks.ts 与 packages/decorators/src/es/hooks.ts每个装饰器把方法写入meta.hooks[EventType.xxx]数组。从 es 版本源码看仓库还额外实现了BeforeUpsert/AfterUpsert钩子与实体 upsert 流程对应。10.1 OnInit()实体新实例创建时触发——无论是em.create()手动创建还是从数据库自动加载。OnInit不会在你直接new MyEntity()构造实体时触发。OnInit() doStuffOnInit() { this.fullName ${this.firstName} - ${this.lastName}; // 初始化影子属性 }10.2 OnLoad()实体从数据库加载时触发。与OnInit()不同它只对完整加载的实体触发不含引用/reference。方法可以为async。OnLoad() async doStuffOnLoad() { // ... }10.3 BeforeCreate() / AfterCreate()BeforeCreate()新实体被写入数据库之前触发AfterCreate()新实体在数据库中创建完成、合并进 Identity Map 之后触发。此时实体已持有EntityManager引用可以调用wrap(entity).init()方法包括所有实体引用与集合。BeforeCreate() async doStuffBeforeCreate() { // ... } AfterCreate() async doStuffAfterCreate() { // ... }10.4 BeforeUpdate() / AfterUpdate()BeforeUpdate()实体在数据库中被更新之前触发AfterUpdate()实体在数据库中被更新之后触发。BeforeUpdate() async doStuffBeforeUpdate() { // ... } AfterUpdate() async doStuffAfterUpdate() { // ... }10.5 BeforeDelete() / AfterDelete()BeforeDelete()记录从数据库删除之前触发。只在移除实体或实体引用时触发通过查询批量删除记录时不会触发AfterDelete()记录删除完成、从 Identity Map 移除之后触发。BeforeDelete() async doStuffBeforeDelete() { // ... } AfterDelete() async doStuffAfterDelete() { // ... }完整的事件体系含事务、flush 时机的详细时序可进一步阅读 events.md仓库中tests/features/events/与tests/features/event-manager/下的测试用例也覆盖了这些钩子的实际触发顺序。十一、事件订阅Subscriber()Subscriber()用于注册事件订阅器是生命周期钩子之外另一种观察实体事件的途径适合把横切逻辑集中到独立类中。注意必须确保该文件被加载例如在某处显式 import 该文件装饰器注册才会生效。Subscriber() export class AuthorSubscriber implements EventSubscriberAuthor { // ... }十二、总结装饰器 → 元数据 → ORM 能力的完整链路把整条链路串起来看MikroORM 的装饰器体系是声明式实体建模的入口声明阶段Entity/Property/PrimaryKey/Enum/Formula/Index/Unique/Check以及四个关系装饰器通过 packages/decorators/src/utils.ts 的getMetadataFromDecorator()把配置写入MetadataStorage的实体元数据对象类级选项如tableName、schema、customRepository、discriminator*属性级选项如fieldName、columnType、cascade、mappedBy消费阶段Schema Generator 读取元数据生成/同步数据库结构SQL 驱动Query Builder 与 Entity Manager 依据元数据构建查询与执行水合Identity Map 依据主键元数据维护对象图事件阶段生命周期钩子与Subscriber()订阅器在em.create、flushinsert/update/delete等环节被EventManager调度为业务留出拦截点。这套设计让实体保持 POJO 本质——不强制继承基类、构造函数只在手动创建时执行受管实体加载不走构造函数除非设置forceConstructor而所有 ORM 语义都沉淀在可审计、可扩展的元数据层中。对装饰器底层细节感兴趣的读者可以直接在仓库中阅读packages/decorators/src/下全部源码并对照tests/features/decorators/目录中的 20 余个测试文件验证每种参数组合的实际行为。赞分享后端【免费下载链接】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 装饰器完全参考从实体定义、属性映射到生命周期钩子与事务方法MikroORM 装饰器完全参考从实体定义、属性映射到生命周期钩子与事务方法 MikroORM 是一个基于 Data Mapper、Unit of Work后端MonSter论文精读CVPR 2025 Highlight背后的核心创新点解析MonSter论文精读CVPR 2025 Highlight背后的核心创新点解析 在计算机视觉领域立体匹配技术一直是深度感知的核心研究方向。今天我们将深入人工智能深度学习计算机视觉Ebook项目贡献指南如何为这个开源电子书库添加新的学习资源Ebook项目贡献指南如何为这个开源电子书库添加新的学习资源 Ebook项目是一个开源电子书库致力于收集和分享各类优质学习资源涵盖算法、前端开发、机器学习上一篇Git2Consul社区贡献指南如何参与开源项目开发与维护下一篇react-hot-toast与PWA集成离线状态下的通知创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。