事件和生命周期钩子
有两种方法可以挂接到实体的生命周期:
¥There are two ways to hook to the lifecycle of an entity:
-
生命周期钩子是在实体原型上定义的方法。
¥Lifecycle hooks are methods defined on an entity prototype.
-
EventSubscribers 是可用于挂接到多个实体的类,或者当你不想在实体原型上存在该方法时使用。
¥EventSubscribers are classes that can be used to hook to multiple entities or when you do not want to have the method present on an entity prototype.
钩子的内部执行方式与订阅者相同。
¥Hooks are internally executed the same way as subscribers.
钩子在订阅者之前执行。
¥Hooks are executed before subscribers.
钩子
¥Hooks
当实体持久化时,你可以使用生命周期钩子来运行任意代码。你可以使用它们标记任何实体方法,并且可以使用相同的钩子标记多个方法。
¥You can use lifecycle hooks to run arbitrary code when an entity gets persisted. You can mark any of entity methods with them, and multiple methods can be marked with the same hook.
所有钩子都支持异步方法,但有一个异常 - @OnInit。
¥All hooks support async methods with one exception - @OnInit.
-
当创建实体的新实例时会触发
@OnInit,无论是手动em.create(),还是从数据库加载新实体时自动触发¥
@OnInitis fired when new instance of entity is created, either manuallyem.create(), or automatically when new entities are loaded from database -
当新实体加载到上下文中时(例如通过
em.find()或em.populate()),会触发@OnLoad。与@OnInit相反,这只会对完全加载的实体而不是引用触发,并且此钩子可以是异步的。¥
@OnLoadis fired when new entity is loaded into context (e.g. viaem.find()orem.populate()). As opposed to@OnInitthis will be fired only for fully loaded entities, not references, and this hook can be async. -
在你将实体持久化到数据库中之前,会触发
@BeforeCreate()和@BeforeUpdate()¥
@BeforeCreate()and@BeforeUpdate()is fired right before you persist an entity in database -
在数据库中更新实体并将其合并到身份映射后,将立即触发
@AfterCreate()和@AfterUpdate()。由于 MikroORM 将每个单个实体关系封装在EntityManager或wrap(entity).init()实例中(出于类型安全),这将使内置的 对任何封装的关系视而不见。¥
@AfterCreate()and@AfterUpdate()is fired right after an entity is updated in database and merged to identity map. Since this event entity will have reference toEntityManagerand will be enabled to callwrap(entity).init()method (including all entity references and collections). -
@BeforeDelete()在你从数据库中删除记录之前触发。它仅在删除实体或实体引用时触发,而不是在通过查询删除记录时触发。¥
@BeforeDelete()is fired right before you delete the record from database. It is fired only when removing entity or entity reference, not when deleting records by query. -
记录从数据库中删除后立即触发
@AfterDelete(),并从身份映射中取消设置。¥
@AfterDelete()is fired right after the record gets deleted from database, and it is unset from the identity map.
当你通过其构造函数(
new MyEntity())手动创建实体时,不会触发@OnInit¥
@OnInitis not fired when you create an entity manually via its constructor (new MyEntity())
@OnInit有时可以触发两次,一次是在创建实体引用时,一次是在填充后。要区分它们,你可以使用wrap(this).isInitialized()。¥
@OnInitcan be sometimes fired twice, once when an entity reference is created, and once after its populated. To distinguish between those you can usewrap(this).isInitialized().
Upsert 钩子
¥Upsert hooks
em.upsert() 和 em.upsertMany 无法触发创建/更新钩子,因为你不知道查询是插入还是更新,这些方法提供自己的钩子 - beforeUpsert 和 afterUpsert。beforeUpsert 事件可能提供 DTO 而不是实体实例,这取决于你如何调用 upsert 方法。你可以使用 EventArgs.meta 对象来检测它属于哪种实体。afterUpsert 事件将始终接收已管理的实体实例。
¥em.upsert() and em.upsertMany cannot fire the create/update hooks, as you don't know if the query is an insert or update, those methods offer their own hooks - beforeUpsert and afterUpsert. The beforeUpsert event might provide a DTO instead of entity instance, based on how you call the upsert method. You can use the EventArgs.meta object to detect what kind of entity it belongs to. afterUpsert event will always receive already managed entity instance.
集合和 @OnUpdate
¥Collections and @OnUpdate
当实体的某些值发生变化并导致 UPDATE 查询时,将触发 @OnUpdate 钩子。这意味着这里只考虑对 M:1 和 1:1 关系的标量属性和拥有方的更改 - 对 Collection 的更改不会触发更新事件。
¥The @OnUpdate hook is fired when some values of an entity change and cause an UPDATE query. This means that only changes to the scalar properties and owning sides of M:1 and 1:1 relations are considered here - changes to Collections won't trigger an update event.
当你修改 1:M 集合时,你实际上是在更改此关系的拥有方,即另一个实体上的 M:1 属性(将触发事件)。
¥When you modify a 1:M collection, you are in fact changing the owning side of this relation, which is the M:1 property on the other entity (which will get the event triggered).
对于具有枢轴实体的 M:N 关系(所有 SQL 驱动程序),你不会在任何一方触发更新事件,因为更改仅针对枢轴表进行。你可以通过 uow.getCollectionUpdates() 获取更新的集合,并通过 Collection.getSnapshot() 检查它们最后已知的数据库状态。
¥For M:N relations with pivot entities (all SQL drivers), you won't get the update event fired on either of the sides, as the changes are made to the pivot table only. You can get the updated collection via uow.getCollectionUpdates(), and check how their last known database state looked like via Collection.getSnapshot().
生命周期钩子的限制
¥Limitations of lifecycle hooks
钩子(以及事件订阅者)在计算所有变更集之后,在工作单元的提交操作中执行。这意味着无法像往常一样从钩子内部创建新实体。从钩子调用 em.flush() 将导致验证错误。调用 em.persist() 可能会导致未定义的行为,如锁定错误。
¥Hooks (as well as event subscribers) are executed inside the commit action of unit of work, after all change sets are computed. This means that it is not possible to create new entities as usual from inside the hook. Calling em.flush() from hooks will result in validation error. Calling em.persist() can result in undefined behavior like locking errors.
The internal
wrap(this, true).__em下可访问的EntityManager实例不供公众使用。¥instance of
EntityManageraccessible underwrap(this, true).__emis not meant for public usage.
EventSubscriber
使用 EventSubscriber 挂接到多个实体,或者如果你不想污染实体原型。所有方法都是可选的,如果省略 getSubscribedEntities() 方法,则表示你正在订阅所有实体。
¥Use EventSubscriber to hook to multiple entities or if you do not want to pollute the entity prototype. All methods are optional, if you omit the getSubscribedEntities() method, it means you are subscribing to all entities.
getSubscribedEntities()对刷新和事务事件没有影响,这些事件始终会被触发,因为刷新未绑定到实体类型。¥
getSubscribedEntities()has no effect on flush and transaction events, those are always fired, since flush is not bound to an entity type.
订阅者通常通过 ORM 配置进行全局注册:
¥Subscribers are normally registered globally, via the ORM config:
MikroORM.init({
subscribers: [new AuthorSubscriber()],
});
或者,你可以通过 em.getEventManager().registerSubscriber() 动态注册它们:
¥Alternatively, you register them dynamically via em.getEventManager().registerSubscriber():
em.getEventManager().registerSubscriber(new AuthorSubscriber());
以下示例展示了一个实现了所有受支持事件的订阅者,由于没有 getSubscribedEntities 方法,因此所有实体类型都会调用它:
¥The following example shows a subscriber which implements all the supported events, and since there is no getSubscribedEntities method, it will be called for all entity types:
import { EventArgs, TransactionEventArgs, EventSubscriber } from '@mikro-orm/core';
export class EverythingSubscriber implements EventSubscriber {
// entity life cycle events
onInit<T>(args: EventArgs<T>): void { ... }
async onLoad<T>(args: EventArgs<T>): Promise<void> { ... }
async beforeCreate<T>(args: EventArgs<T>): Promise<void> { ... }
async afterCreate<T>(args: EventArgs<T>): Promise<void> { ... }
async beforeUpdate<T>(args: EventArgs<T>): Promise<void> { ... }
async afterUpdate<T>(args: EventArgs<T>): Promise<void> { ... }
async beforeUpsert<T>(args: EventArgs<T>): Promise<void> { ... }
async afterUpsert<T>(args: EventArgs<T>): Promise<void> { ... }
async beforeDelete<T>(args: EventArgs<T>): Promise<void> { ... }
async afterDelete<T>(args: EventArgs<T>): Promise<void> { ... }
// flush events
async beforeFlush<T>(args: FlushEventArgs): Promise<void> { ... }
async onFlush<T>(args: FlushEventArgs): Promise<void> { ... }
async afterFlush<T>(args: FlushEventArgs): Promise<void> { ... }
// transaction events
async beforeTransactionStart(args: TransactionEventArgs): Promise<void> { ... }
async afterTransactionStart(args: TransactionEventArgs): Promise<void> { ... }
async beforeTransactionCommit(args: TransactionEventArgs): Promise<void> { ... }
async afterTransactionCommit(args: TransactionEventArgs): Promise<void> { ... }
async beforeTransactionRollback(args: TransactionEventArgs): Promise<void> { ... }
async afterTransactionRollback(args: TransactionEventArgs): Promise<void> { ... }
}
EventArgs
作为钩子方法的参数,你将获得 EventArgs 实例。它将始终包含对当前 EntityManager 和特定实体的引用。在刷新操作期间从 UnitOfWork 触发的事件也包含 ChangeSet 对象。
¥As a parameter to the hook method you get EventArgs instance. It will always contain reference to the current EntityManager and the particular entity. Events fired from UnitOfWork during flush operation also contain the ChangeSet object.
interface EventArgs<T> {
entity: T;
em: EntityManager;
changeSet?: ChangeSet<T>;
}
interface ChangeSet<T> {
name: string; // entity name
collection: string; // db table name
type: ChangeSetType; // type of operation
entity: T; // up to date entity instance
payload: EntityData<T>; // changes that will be used to build the update query
persisted: boolean; // whether the changeset was already persisted/executed
originalEntity?: EntityData<T>; // snapshot of an entity when it was loaded from db
}
enum ChangeSetType {
CREATE = 'create',
UPDATE = 'update',
DELETE = 'delete',
DELETE_EARLY = 'delete_early',
}
刷新事件
¥Flush events
在提交阶段(刷新操作)会执行一种特殊类型的事件。它们在刷新之前、期间和之后执行,并且不绑定到任何特定实体。
¥There is a special kind of events executed during the commit phase (flush operation). They are executed before, during and after the flush, and they are not bound to any entity in particular.
-
beforeFlush在计算变更集之前执行,这是唯一可以安全持久化新实体的事件。¥
beforeFlushis executed before change sets are computed, this is the only event where it is safe to persist new entities. -
onFlush在计算变更集后执行。¥
onFlushis executed after the change sets are computed. -
afterFlush在flush调用解析之前作为最后一步执行。即使没有要刷新的更改,它也会执行。¥
afterFlushis executed as the last step just before theflushcall resolves. it will be executed even if there are no changes to be flushed.
刷新事件参数将不包含任何实体实例,因为它们与实体无关。它们确实包含对 UnitOfWork 实例的额外引用。
¥Flush event args will not contain any entity instance, as they are entity agnostic. They do contain additional reference to the UnitOfWork instance.
interface FlushEventArgs extends Omit<EventArgs<unknown>, 'entity'> {
uow?: UnitOfWork;
}
刷新事件与实体无关,指定
getSubscribedEntities()方法不会对它们产生任何影响。它们在每个flush操作中仅触发一次。¥Flush events are entity agnostic, specifying
getSubscribedEntities()method will not have any effect for those. They are fired only once per theflushoperation.
事务事件
¥Transaction events
你还可以利用数据库事务事件:
¥You can also tap into the database transaction events:
-
beforeTransactionStart -
afterTransactionStart -
beforeTransactionCommit -
afterTransactionCommit -
beforeTransactionRollback -
afterTransactionRollback
事务事件参数将不包含任何实体实例,因为它们与实体无关。它们确实包含对 UnitOfWork 实例和原生 Transaction 对象的额外引用(例如,对于 SQL 驱动程序,它将是 knex 客户端实例)。
¥Transaction event args will not contain any entity instance, as they are entity agnostic. They do contain additional reference to the UnitOfWork instance and native Transaction object (e.g. for SQL drivers it will be knex client instance).
export interface TransactionEventArgs extends Omit<EventArgs<unknown>, 'entity' | 'changeSet'> {
transaction?: Transaction;
uow?: UnitOfWork;
}
从 UnitOfWork 获取更改
¥Getting the changes from UnitOfWork
你可以通过这些方法观察给定 UnitOfWork 的所有更改:
¥You can observe all the changes that are part of given UnitOfWork via those methods:
UnitOfWork.getChangeSets(): ChangeSet<AnyEntity>[];
UnitOfWork.getOriginalEntityData(entity): EntityData<AnyEntity>;
UnitOfWork.getPersistStack(): Set<AnyEntity>;
UnitOfWork.getRemoveStack(): Set<AnyEntity>;
UnitOfWork.getCollectionUpdates(): Collection<AnyEntity>[];
UnitOfWork.getExtraUpdates(): Set<[AnyEntity, string, (AnyEntity | Reference<AnyEntity>)]>;
使用 onFlush 事件
¥Using onFlush event
在以下示例中,我们有 2 个实体:FooBar 和 FooBaz,通过 M:1 关系连接。当我们在更改集中检测到它时,我们的订阅者将自动创建新的 FooBaz 实体并将其连接到 FooBar。
¥In following example we have 2 entities: FooBar and FooBaz, connected via M:1 relation. Our subscriber will automatically create new FooBaz entity and connect it to the FooBar when we detect it in the change sets.
我们首先使用 uow.getChangeSets() 方法查找我们感兴趣的实体的更改集。创建 FooBaz 实例并将其与 FooBar 链接后,我们需要做两件事:
¥We first use uow.getChangeSets() method to look up the change set of entity we are interested in. After we create the FooBaz instance and link it with FooBar, we need to do two things:
-
调用
uow.computeChangeSet(baz)计算新创建的FooBaz实体的变更集¥Call
uow.computeChangeSet(baz)to compute the change set of newly createdFooBazentity -
调用
uow.recomputeSingleChangeSet(cs.entity)重新计算FooBar实体的现有变更集。¥Call
uow.recomputeSingleChangeSet(cs.entity)to recalculate the existing change set of theFooBarentity.
export class FooBarSubscriber implements EventSubscriber {
async onFlush(args: FlushEventArgs): Promise<void> {
const changeSets = args.uow.getChangeSets();
const cs = changeSets.find(cs => cs.type === ChangeSetType.CREATE && cs.entity instanceof FooBar);
if (cs) {
const baz = new FooBaz();
baz.name = 'dynamic';
cs.entity.baz = baz;
args.uow.computeChangeSet(baz);
args.uow.recomputeSingleChangeSet(cs.entity);
}
}
}
const bar = new FooBar();
bar.name = 'bar';
await em.persist(bar).flush();
要创建 DELETE 变更集,你可以使用 uow.computeChangeSet() 的第二个参数:
¥To create a DELETE changeset, you can use the second parameter of uow.computeChangeSet():
async onFlush(args: FlushEventArgs): Promise<void> {
const changeSets = args.uow.getChangeSets();
const cs = changeSets.find(cs => cs.type === ChangeSetType.UPDATE && cs.entity instanceof FooBar);
if (cs) {
args.uow.computeChangeSet(cs.entity, ChangeSetType.DELETE);
}
}
事务事件
¥Transaction events
事务事件发生在事务的开始和结束时。
¥Transaction events happen at the beginning and end of a transaction.
-
beforeTransactionStart在事务开始前执行。¥
beforeTransactionStartis executed before a transaction starts. -
afterTransactionStart在事务开始后执行。¥
afterTransactionStartis executed after a transaction starts. -
beforeTransactionCommit在事务提交前执行。¥
beforeTransactionCommitis executed before a transaction is committed. -
afterTransactionCommit在事务提交后执行。¥
afterTransactionCommitis executed after a transaction is committed. -
beforeTransactionRollback在事务回滚前执行。¥
beforeTransactionRollbackis executed before a transaction is rolled back. -
afterTransactionRollback在事务回滚后执行。¥
afterTransactionRollbackis executed after a transaction is rolled back.
它们也是实体不可知的,只会引用事务、UnitOfWork 实例和 EntityManager 实例。
¥They are also entity agnostic and will only reference the transaction, UnitOfWork instance and EntityManager instance.