
后端【免费下载链接】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 7.1 的事件与生命周期钩子Events and Lifecycle Hooks为核心系统讲解实体级钩子Lifecycle Hooks与事件订阅者EventSubscribers两种监听方式、全部 16 类事件的触发时机、EventArgs/ChangeSet数据结构以及如何在onFlush中动态改写变更集实现软删除、在beforeFlush中安全创建新实体等高级实战场景。读完本文你将能够为单个实体注册钩子、为跨实体场景编写全局订阅者并在 Unit of Work 提交阶段安全地介入 CRUD 流程。两种监听机制Lifecycle Hooks 与 EventSubscribersMikroORM 提供了两种挂接实体生命周期的方式见 events.md生命周期钩子Lifecycle Hooks定义在实体类上的方法在实体生命周期的特定节点执行。它天然与实体绑定适合这个实体自己的逻辑如生成 slug、更新时间戳。事件订阅者EventSubscribers独立的类可以同时监听多个实体的同类事件适合横切关注点审计日志、全局统计、软删除。两条路径支持完全相同的事件集合且执行顺序固定先执行实体钩子再执行订阅者。这一顺序在 EventManager.ts 的dispatchEvent中体现——先收集meta.hooks[event]中的实体钩子再追加匹配的订阅者最后通过Utils.runSerial串行执行。可用事件总览所有事件枚举定义在 enums.ts 的EventType中事件触发时机onInit实体实例创建时em.create()或从数据库加载onLoad实体从数据库完全加载后引用 reference 不触发beforeCreate新实体插入数据库之前afterCreate新实体插入并合并进 Identity Map 之后beforeUpdate已有实体更新数据库之前afterUpdate实体更新、变更合并之后beforeUpsertem.upsert()或em.upsertMany()执行之前afterUpsertupsert 完成后收到受管实体beforeDelete实体从数据库删除之前afterDelete实体删除并从 Identity Map 移除之后此外还有与实体无关的Flush 事件beforeFlush/onFlush/afterFlush与事务事件beforeTransactionStart/afterTransactionStart/beforeTransactionCommit/afterTransactionCommit/beforeTransactionRollback/afterTransactionRollback详见下文。定义生命周期钩子MikroORM 7.1 支持四种实体定义方式钩子注册方式随之不同。以下均以经典的Article实体含title、唯一slug与updatedAt为例。方式一defineEntity class推荐类型安全最佳先用defineEntity定义 Schema再导出类、调用setClass关联最后用addHook注册钩子import { defineEntity, type EventArgs, p } from mikro-orm/core; const ArticleSchema defineEntity({ name: Article, properties: { id: p.integer().primary(), title: p.string(), slug: p.string().unique(), updatedAt: p.datetime(), }, }); export class Article extends ArticleSchema.class {} ArticleSchema.setClass(Article); ArticleSchema.addHook(beforeCreate, async (args: EventArgsArticle) { const article args.entity; if (!article.slug) { article.slug article.title.toLowerCase().replace(/\s/g, -); } }); ArticleSchema.addHook(beforeUpdate, async (args: EventArgsArticle) { args.entity.updatedAt new Date(); });为什么用addHook而不是内联hooks属性官方文档明确指出内联hooks属性中的args.entity会被推断为any因为此时实体类型尚未定义显式标注EventArgsArticle又会造成循环引用。addHook的实现见 EntitySchema.ts是在实体定义完成后把处理器 push 进_meta.hooks[event]此时类型已闭合可获得完整类型安全。方式二defineEntity无类不导出类、只通过InferEntity推导实体类型时同样使用addHookimport { defineEntity, type InferEntity, type EventArgs, p } from mikro-orm/core; export const Article defineEntity({ name: Article, properties: { id: p.integer().primary(), title: p.string(), slug: p.string().unique(), updatedAt: p.datetime(), }, }); export type IArticle InferEntitytypeof Article; Article.addHook(beforeCreate, async (args: EventArgsIArticle) { const article args.entity; if (!article.slug) { article.slug article.title.toLowerCase().replace(/\s/g, -); } }); Article.addHook(beforeUpdate, async (args: EventArgsIArticle) { args.entity.updatedAt new Date(); });方式三与方式四装饰器reflect-metadata / ts-morph使用装饰器时直接在实体方法上标注BeforeCreate()、BeforeUpdate()等钩子装饰器即可import { Entity, PrimaryKey, Property, BeforeCreate, BeforeUpdate } from mikro-orm/core; Entity() export class Article { PrimaryKey() id!: number; Property() title!: string; Property({ unique: true }) slug!: string; Property() updatedAt?: Date; BeforeCreate() generateSlug() { if (!this.slug) { this.slug this.title.toLowerCase().replace(/\s/g, -); } } BeforeUpdate() updateTimestamp() { this.updatedAt new Date(); } }注意同一个钩子装饰器可以标注多个方法在钩子方法内部this指向实体实例本身。从源码看钩子执行时通过handler.bind(entity)绑定实体见 EventManager.ts无论钩子是方法名还是函数都会被正确调用。钩子方法签名所有钩子接收一个EventArgs对象且除onInit外都可以是异步的async function myHook(args: EventArgsMyEntity): Promisevoid { const entity args.entity; // 实体实例 const em args.em; // EntityManager const changeSet args.changeSet; // flush 期间create/update/delete可用 }特殊钩子的注意事项onInit在em.create()创建或从数据库加载实体时触发直接new Entity()不会触发引用reference场景可能触发两次创建引用时一次、填充 populate 时一次可用wrap(this).isInitialized()区分必须是同步的——这一点在 EventManager.ts 中体现onInit事件对每个 listener 直接void listener(args)执行而不 await。onLoad仅对完全加载的实体触发引用reference不触发可以是异步的。beforeUpdate/afterUpdate仅当标量属性或关系拥有方owning side发生变化时触发集合Collection的变更不会触发更新事件原因见下节。Upsert 专属钩子由于em.upsert()在真正执行前无法预知是插入还是更新因此它有独立钩子beforeUpsert—— 可能收到DTO而非实体实例afterUpsert—— 总是收到受管的实体实例。收到 DTO 时可用EventArgs.meta识别实体类型meta字段在 EventSubscriber.ts 中定义为EntityMetadataT包含实体名称、表名、属性等完整元数据。集合变更与更新事件beforeUpdate/afterUpdate只有在生成了UPDATE查询时才触发而这只发生在标量属性变化M:1 与 1:1 关系的拥有方变化。集合变更不触发更新事件的原因1:M 关系变更体现在关联实体多的一方的 M:1 侧事件由关联实体触发M:N 关系变更只影响中间表pivot table。若要观察 flush 期间的集合变更应在 flush 事件订阅者中使用uow.getCollectionUpdates()实现见 UnitOfWork.ts返回所有待同步的 M:N 集合。钩子的执行限制钩子在 Unit of Work 提交阶段、变更集计算完成后执行对应UnitOfWork中runHooks调用dispatchEvent的位置见 UnitOfWork.ts。因此不要调用em.flush()—— 会抛出校验错误不要调用em.persist()—— 可能导致未定义行为需要创建新实体时改用beforeFlush事件见下文Flush 事件。EventSubscribers跨实体的事件订阅当需要以下能力时选择EventSubscriber监听多个实体类型的事件将事件逻辑与实体定义解耦访问包含变更集的完整EventArgs。注册订阅者全局注册ORM 配置MikroORM.init({ subscribers: [new ArticleSubscriber(), new AuditSubscriber()], });或运行时动态注册em.getEventManager().registerSubscriber(new ArticleSubscriber());EventManager.registerSubscriber的实现EventManager.ts会索引订阅者的实体集合与事件类型把实现了的事件方法挂到对应EventType的监听器 Set 中并清空hasListeners的缓存。dispatchEvent中按钩子 → 订阅者顺序串行执行。编写订阅者只订阅特定实体import { EventSubscriber, EventArgs } from mikro-orm/core; import { Article } from ./entities/Article.js; export class ArticleSubscriber implements EventSubscriberArticle { // 只订阅 Article 的事件 getSubscribedEntities() { return [Article]; } async beforeCreate(args: EventArgsArticle) { console.log(Creating article:, args.entity.title); } async afterUpdate(args: EventArgsArticle) { // args.changeSet 包含变更内容 console.log(Updated fields:, Object.keys(args.changeSet?.payload ?? {})); } }省略getSubscribedEntities()则订阅所有实体export class AuditSubscriber implements EventSubscriber { async afterCreate(args: EventArgsunknown) { console.log(Created:, args.changeSet?.name, args.changeSet?.entity); } }完整的订阅者接口EventSubscriber的全部可选方法见 EventSubscriber.tsimport { EventArgs, FlushEventArgs, TransactionEventArgs, EventSubscriber } from mikro-orm/core; export class FullSubscriber implements EventSubscriber { // 实体生命周期事件 onInitT(args: EventArgsT): void { } async onLoadT(args: EventArgsT): Promisevoid { } async beforeCreateT(args: EventArgsT): Promisevoid { } async afterCreateT(args: EventArgsT): Promisevoid { } async beforeUpdateT(args: EventArgsT): Promisevoid { } async afterUpdateT(args: EventArgsT): Promisevoid { } async beforeUpsertT(args: EventArgsT): Promisevoid { } async afterUpsertT(args: EventArgsT): Promisevoid { } async beforeDeleteT(args: EventArgsT): Promisevoid { } async afterDeleteT(args: EventArgsT): Promisevoid { } // Flush 事件 async beforeFlush(args: FlushEventArgs): Promisevoid { } async onFlush(args: FlushEventArgs): Promisevoid { } async afterFlush(args: FlushEventArgs): Promisevoid { } // 事务事件 async beforeTransactionStart(args: TransactionEventArgs): Promisevoid { } async afterTransactionStart(args: TransactionEventArgs): Promisevoid { } async beforeTransactionCommit(args: TransactionEventArgs): Promisevoid { } async afterTransactionCommit(args: TransactionEventArgs): Promisevoid { } async beforeTransactionRollback(args: TransactionEventArgs): Promisevoid { } async afterTransactionRollback(args: TransactionEventArgs): Promisevoid { } }EventArgs 与 ChangeSet 数据结构事件处理器接收的EventArgs源码定义见 EventSubscriber.tsinterface EventArgsT { entity: T; // 实体实例 em: EntityManager; // EntityManager meta: EntityMetadataT; // 实体元数据含名称、表名、属性等 changeSet?: ChangeSetT; // flush 操作期间可用 }ChangeSet表示单个实体的待执行变更实现见 ChangeSet.tsinterface ChangeSetT { name: string; // 实体名称 collection: string; // 数据库表名 type: ChangeSetType; // create | update | delete | delete_early | update_early entity: T; // 实体实例 payload: EntityDataT; // UPDATE 查询的变更字段 persisted: boolean; // 是否已执行 originalEntity?: EntityDataT; // 从数据库加载时的快照 }ChangeSetType枚举实际包含五个值ChangeSet.tscreate、update、delete、update_early、delete_early。文档示例中提及的delete_early即对应后者。Flush 事件介入提交阶段的关键Flush 事件在em.flush()期间触发不与任何具体实体绑定事件触发时机典型用途beforeFlush变更集计算之前可安全地 persist 新实体onFlush变更集计算之后修改或新增变更集afterFlush所有查询执行完成之后清理、通知参数类型interface FlushEventArgs extends OmitEventArgsunknown, entity { uow: UnitOfWork; }注意getSubscribedEntities()对 Flush 事件无效——无论实体类型过滤如何Flush 事件始终触发。这一行为与 EventManager.ts 的过滤逻辑一致过滤仅针对带entity参数的事件而 Flush/事务事件不带实体。在UnitOfWork.commit流程中三个事件分别在computeChangeSets()之前、之后及数据库写入完成后派发见 UnitOfWork.ts。在 Flush 事件中查看待处理变更UnitOfWork提供了若干查询方法UnitOfWork.tsasync onFlush(args: FlushEventArgs) { const uow args.uow; // 所有待处理变更集 const changeSets uow.getChangeSets(); // 实体加载时的原始数据 const original uow.getOriginalEntityData(entity); // 标记为 persist / remove 的实体 const toInsert uow.getPersistStack(); const toDelete uow.getRemoveStack(); // 集合修改 const collectionUpdates uow.getCollectionUpdates(); }在 beforeFlush 中安全创建实体async beforeFlush(args: FlushEventArgs) { // 在这里创建并 persist 新实体是安全的 const log args.em.create(AuditLog, { action: flush, timestamp: new Date() }); }在 onFlush 中修改变更集新增关联实体并重算变更集async onFlush(args: FlushEventArgs) { const changeSets args.uow.getChangeSets(); const cs changeSets.find(cs cs.type ChangeSetType.CREATE cs.name FooBar ); if (cs) { // 创建一个关联实体 const related new FooBaz(); related.name auto-created; cs.entity.baz related; // 为新实体计算变更集 args.uow.computeChangeSet(related); // 重新计算原实体的变更集 args.uow.recomputeSingleChangeSet(cs.entity); } }computeChangeSetUnitOfWork.ts会通过变更集计算器生成新的ChangeSet并注册进#changeSetsrecomputeSingleChangeSetUnitOfWork.ts则对已跟踪实体重新计算并合并 payload——从源码看它还专门处理了 TPTtable-per-type继承场景下父表列不泄漏进叶子表的问题。将删除转换为更新软删除async onFlush(args: FlushEventArgs) { for (const cs of args.uow.getChangeSets()) { if (cs.type ! ChangeSetType.DELETE) { continue; } if (!cs.meta.properties.deletedAt) { continue; } cs.entity.deletedAt new Date(); args.uow.computeChangeSet(cs.entity, ChangeSetType.UPDATE); } }将更新转换为删除async onFlush(args: FlushEventArgs) { const cs args.uow.getChangeSets().find(cs cs.type ChangeSetType.UPDATE cs.entity.shouldDelete ); if (cs) { args.uow.computeChangeSet(cs.entity, ChangeSetType.DELETE); } }从源码看computeChangeSet对DELETE/DELETE_EARLY会直接注册一个空 payload 的删除变更集UnitOfWork.ts因此cs.entity.deletedAt new Date()后调用computeChangeSet(cs.entity, ChangeSetType.UPDATE)会基于实体当前状态重新生成带deletedAt的 UPDATE payload实现软删除的优雅落地。事务事件事务事件在事务边界触发事件触发时机beforeTransactionStart事务开始之前afterTransactionStart事务开始之后beforeTransactionCommit事务提交之前afterTransactionCommit事务提交之后beforeTransactionRollback事务回滚之前afterTransactionRollback事务回滚之后参数类型EventSubscriber.tsinterface TransactionEventArgs extends OmitEventArgsunknown, entity | changeSet { transaction?: Transaction; // 原生事务SQL 驱动下为 Kysely 实例 uow?: UnitOfWork; }事务事件同样与实体无关——getSubscribedEntities()对其无效。派发路径在 TransactionEventBroadcaster.ts 中实现适合埋点监控、分布式追踪等横切需求。总结何时选择哪种机制场景推荐机制单个实体的固有逻辑slug 生成、时间戳实体钩子addHook或装饰器跨实体审计、日志、统计EventSubscriber在提交前创建关联实体、实现软删除、改写变更集Flush 事件订阅者beforeFlush/onFlush事务边界埋点、追踪事务事件订阅者两条监听路径共享同一套事件类型与EventManager派发管线实体钩子永远先于订阅者执行。理解「钩子在 UoW 提交阶段执行、不得嵌套 flush/persist」这一核心约束再配合onFlush中computeChangeSet/recomputeSingleChangeSet的变更集改写能力即可安全、优雅地在 MikroORM 7.1 中实现绝大多数业务横切逻辑。相关测试与更多示例可参考 tests/features/events 与 tests/features/event-manager 目录。赞分享后端【免费下载链接】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 事件系统与生命周期钩子完全指南Hooks、EventSubscriber 与 Flush/Transaction 事件实战MikroORM 事件系统与生命周期钩子完全指南Hooks、EventSubscriber 与 Flush/Transaction 事件实战 MikroORM后端Sigma.js 事件系统完全指南从交互事件到生命周期钩子的深度解析Sigma.js 事件系统完全指南从交互事件到生命周期钩子的深度解析 Sigma.js 是一个面向大规模图可视化的 JavaScript 库其事件系统是驱动数据可视化前端图形学mikro-orm 实体生命周期事件与钩子Events Lifecycle Hooks完全指南mikro orm 实体生命周期事件与钩子Events Lifecycle Hooks完全指南 本篇技术指南围绕 docs/docs/events.md后端上一篇Hello-Python项目中的在线支付提款问题分析与技术探讨下一篇终极指南mdp 256色渐变效果与透明终端支持的完整解析 创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考