Skip to the content.

Hooks - 钩子

Hook 是你可以监听的事件, 当调用相应的方法时会触发这些事件.

它们对于向应用程序的核心添加自定义功能很有用. 例如, 如果你想在保存模型之前始终在模型上设置一个值, 你可以监听模型的 beforeUpdate hook.

静态 Sequelize Hook

Sequelize 类支持以下 hook:

Hook name Async Run when
beforeInit, afterInit ❌ 创建一个 sequelize 实例

示例:

import { Sequelize } from '@sequelize/core';

Sequelize.hooks.addListener('beforeInit', () => {
  console.log('A new sequelize instance is being created');
});

实例 Sequelize Hook

Sequelize 实例支持以下 hook:

Hook name Async Run when
beforeDefine, afterDefine ❌ 正在注册一个新的 Model 类
beforeQuery, afterQuery ✅ 正在运行 SQL 查询
beforeBulkSync, afterBulkSync ✅ sequelize.sync 被调用
beforeConnect, afterConnect ✅ 每当创建与数据库的新连接时
beforeDisconnect, afterDisconnect ✅ 每当关闭与数据库的连接时

注意

所有 [模型 hook] 也会在 sequelize 实例上触发:

import { Sequelize } from '@sequelize/core';

const sequelize = new Sequelize(/* options */);

// 每当在任何模型上调用 findAll 时都会调用它
sequelize.hooks.addListener('beforeFind', () => {
  console.log('findAll 已被模型调用');
});

注册 Sequelize Hook

实例 Sequelize hook 可以通过两种方式注册:

  1. 使用 Sequelize 实例的 hooks 属性:

    import { Sequelize } from '@sequelize/core';
      
    const sequelize = new Sequelize(/* options */);
      
    // highlight-next-line
    sequelize.hooks.addListener('beforeDefine', () => {
      console.log('正在初始化一个新模型');
    });
    
  2. 通过 Sequelize 参数:

    import { Sequelize } from '@sequelize/core';
      
    const sequelize = new Sequelize({
      // highlight-next-line
      hooks: {
        beforeDefine: () => {
          console.log('正在初始化一个新模型');
        },
      },
    });
    

模型 Sequelize Hook

Model 类支持以下 hook:

Hook name Async Run when
beforeAssociate, afterAssociate ❌ 每当在模型上声明关联时
beforeSync, afterSync ✅ 当调用 sequelize.sync 或 Model.sync 时
beforeValidate, afterValidate, validationFailed ✅ 当验证模型的属性时(发生在大多数模型方法中)
beforeFind, beforeFindAfterExpandIncludeAll, beforeFindAfterOptions, afterFind ✅ 当调用 Model.findAll 时1
beforeCount ✅ 当调用 Model.count 时
beforeUpsert, afterUpsert ✅ 当调用 Model.upsert 时
beforeBulkCreate, afterBulkCreate ✅ 当调用 Model.bulkCreate 时
beforeBulkDestroy, afterBulkDestroy ✅ 当调用 Model.destroy 时
beforeDestroy, afterDestroy ✅ 当调用 Model#destroy 时
beforeBulkRestore, afterBulkRestore ✅ 当调用 Model.restore 时
beforeRestore, afterRestore ✅ 当调用 Model#restore 时
beforeBulkUpdate, afterBulkUpdate ✅ 当调用 Model.update 时
beforeUpdate, afterUpdate ✅ 当调用 Model#update 或 Model#save 时(并且模型不是新的)
beforeCreate, afterCreate ✅ 当调用 Model#save 时(并且模型是新的)
beforeSave, afterSave ✅ 当调用 Model#update 或 Model#save 时

注册模型 hook

实例 Sequelize hook 可以通过三种方式注册:

  1. 使用 Sequelize 实例的 hooks 属性:

    import { Sequelize, DataTypes } from '@sequelize/core';
        
    const sequelize = new Sequelize(/* options */);
        
    const MyModel = sequelize.define('MyModel', {
      name: DataTypes.STRING,
    });
       
    // highlight-next-line
    MyModel.hooks.addListener('beforeFind', () => {
      console.log('findAll has been called on MyModel');
    });
    
  2. 通过 Model.init 或 sequelize.define 的参数:

    import { DataTypes } from '@sequelize/core';
    
    const MyModel = sequelize.define('MyModel', {
      name: DataTypes.STRING,
    }, {
      // highlight-next-line
      hooks: {
        beforeFind: () => {
          console.log('findAll has been called on MyModel');
        },
      },
    });
    
  3. 使用装饰器

    import { Sequelize, Model, Hook } from '@sequelize/core';
    import { BeforeFind } from '@sequelize/core/decorators-legacy';
       
    export class MyModel extends Model {
      // highlight-next-line
      @BeforeFind
      static logFindAll() {
        console.log('findAll has been called on MyModel');
      }
    }
    

同步 vs 异步 Hook

在上表中, 你可以看到一些挂钩被标记为 Async. 这意味着你的侦听器可以返回一个将在继续执行 hook 之前等待的 Promise.

在 hook 中进行异步操作时要小心. hook 是连续运行的, 在你的 hook 被解析之前不会运行下一个 hook. 如果你有很多 hook, 这可能会导致性能问题.

尝试从同步 hook 返回一个 Promise 会引发错误.

移除 Hook

你可以使用 hooks.removeListener 并提供与 hooks.addListener 相同的回调或你的侦听器名称来移除 hook:

class Book extends Model {}
Book.init({
  title: DataTypes.STRING
}, { sequelize });

const myListener = (book, options) => {
  // ...
};

Book.hooks.addListener('afterCreate', 'yourHookIdentifier', myListener);

// 这两种都将删除 hook:
// success-next-line
Book.hooks.removeListener('afterCreate', myListener);
// success-next-line
Book.hooks.removeListener('afterCreate', 'yourHookIdentifier');

注意

装饰器添加的 hook 不能使用回调实例删除, 但如果它们被命名仍然可以删除:

import { Sequelize, Model, Hook } from '@sequelize/core';
import { BeforeFind } from '@sequelize/core/decorators-legacy';

export class MyModel extends Model {
  @BeforeFind({ name: 'yourHookIdentifier' })
  static logFindAll() {
    console.log('findAll has been called on MyModel');
  }
}

// 这行不通
// error-next-line
MyModel.hooks.removeListener('beforeFind', MyModel.logFindAll);

// 但是这种可以
// success-next-line
MyModel.hooks.removeListener('beforeFind', 'yourHookIdentifier');

但是, 我们不建议删除通过装饰器添加的 hook, 因为这可能会使你的代码更难理解.

关联方法 Hook

关联 在你的模型上添加的方法确实提供了特定于它们的 hook, 但它们是建立在顶部的 常规模型方法, 这将触发 hook.

例如, 使用 add / set 混合方法将触发 beforeUpdate 和 afterUpdate hook.

individualHooks

注意

不鼓励使用此参数有两个原因:

小心使用. 如果你需要对数据库更改做出应对, 请考虑改用数据库的触发器和通知系统.

当使用 Model.destroy 或 Model.update 等静态模型方法时, 只会调用它们对应的 “批量” hook(例如 beforeBulkDestroy), 而不是实例 hook (例如 beforeDestroy).

如果要触发实例 hook, 请使用方法的 individualHooks 参数为将受到影响的每一行运行实例 hook.

以下示例将为将要删除的每一行触发 beforeDestroy 和 afterDestroy hook:

User.destroy({
  where: {
    id: [1, 2, 3],
  },
  individualHooks: true,
});

例外情况

只有 Model 方法 触发hook. 这意味着在许多情况下, Sequelize 将与数据库交互而不触发 hook.

这些包括但不限于:

如果你需要对这些事件做出应对, 请考虑改用数据库的本机和通知系统.

级联删除的 Hook

如 [例外情况] 中所示, 由于 ON DELETE CASCADE 约束, 当数据库删除实例时, Sequelize 不会触发挂钩.

但是, 如果你在定义关联时将 hooks 参数设置为 true, Sequelize 将为已删除的实例触发 beforeDestroy 和 afterDestroy hook.

注意

由于以下原因, 不鼓励使用此参数:

此参数被视为遗留选项. 如果你需要收到数据库更改通知, 我们强烈建议你使用数据库的触发器和通知系统.

以下是如何使用此参数的示例:

import { Model } from '@sequelize/core';
import { HasMany, BeforeDestroy } from '@sequelize/core/decorators-legacy';

class User extends Model {
  // 这个"hook"参数将导致"beforeDestroy"和"afterDestroy"
  // highlight-next-line
  @HasMany(() => Post, { hooks: true })
  declare posts: Post[];
}

class Post extends Model {
  @BeforeDestroy
  static logDestroy() {
    console.log('帖子已被销毁');
  }
}

const sequelize = new Sequelize({
  /* 参数 */
  models: [User, Post], 
});

await sequelize.sync({ force: true });

const user = await User.create();
const post = await Post.create({ userId: user.id });

// 这会输出 "帖子已被销毁"
await user.destroy();

Hook 和 事务

Sequelize 中的许多模型操作都支持在方法的参数中指定事务. 如果在原始调用中 指定 了事务, 它将出现在传递给 hook 函数的参数中.

例如, 考虑以下代码片段:

User.hooks.addListener('afterCreate', async (user, options) => {
  // 我们可以使用 `options.transaction` 
  // 使用与触发此 hook 的调用相同的事务来执行其他调用
  await User.update({ mood: 'sad' }, {
    where: {
      id: user.id
    },
    // highlight-next-line
    transaction: options.transaction
  });
});

await sequelize.transaction(async transaction => {
  await User.create({
    username: 'someguy',
    mood: 'happy'
  }, {
    transaction,
  });
});

如果我们在前面的代码中调用 User.update 时没有包含事务参数, 则不会发生任何变化, 因为我们新创建的用户在待处理事务之前不存在于数据库中.

  1. findAll: 请注意, 一些方法, 例如 Model.findOne、Model.findAndCountAll 和关联获取器也会在内部调用 Model.findAll. 这也会导致为这些方法调用 beforeFind hook. ↩