Hooks - 钩子

    获取完整列表, 请查看 Hooks file.

    声明 Hook

    Hook 的参数通过引用传递。 这意味着您可以更改值,这将反映在insert / update语句中。 Hook 可能包含异步动作 - 在这种情况下,Hook 函数应该返回一个 promise。

    目前有三种以编程方式添加 hook 的方法:

    1. // 方法1 通过 .define() 方法
    2. const User = sequelize.define('user', {
    3. username: DataTypes.STRING,
    4. mood: {
    5. type: DataTypes.ENUM,
    6. values: ['happy', 'sad', 'neutral']
    7. }
    8. }, {
    9. hooks: {
    10. beforeValidate: (user, options) => {
    11. user.mood = 'happy';
    12. },
    13. afterValidate: (user, options) => {
    14. user.username = 'Toni';
    15. }
    16. }
    17. });
    18. // 方法2 通过 . hook() 方法 (或其别名 .addHook() 方法)
    19. User.hook('beforeValidate', (user, options) => {
    20. user.mood = 'happy';
    21. });
    22. User.addHook('afterValidate', 'someCustomName', (user, options) => {
    23. return sequelize.Promise.reject(new Error("I'm afraid I can't let you do that!"));
    24. });
    25. // 方法3 通过直接方法
    26. User.beforeCreate((user, options) => {
    27. return hashPassword(user.password).then(hashedPw => {
    28. user.password = hashedPw;
    29. });
    30. });
    31. User.afterValidate('myHookAfter', (user, options) => {
    32. user.username = 'Toni';
    33. });

    只能删除有名称参数的 hook。

    1. const Book = sequelize.define('book', {
    2. title: DataTypes.STRING
    3. });
    4. Book.addHook('afterCreate', 'notifyUsers', (book, options) => {
    5. // ...
    6. });
    7. Book.removeHook('afterCreate', 'notifyUsers');

    你可以有很多同名的 hook。 调用 .removeHook() 将会删除它们。

    全局 / 通用 Hook

    全局 hook 是所有模型的 hook。 他们可以定义您想要的所有模型的行为,并且对插件特别有用。 它们可以用两种方式来定义,它们的语义略有不同:

    1. const sequelize = new Sequelize(..., {
    2. define: {
    3. beforeCreate: () => {
    4. // 做些什么
    5. }
    6. }
    7. }
    8. });

    这将为所有模型添加一个默认 hook,如果模型没有定义自己的 beforeCreate hook,那么它将运行。

    1. const User = sequelize.define('user');
    2. const Project = sequelize.define('project', {}, {
    3. hooks: {
    4. beforeCreate: () => {
    5. // 做些其它什么
    6. }
    7. });
    8. User.create() // 运行全局 hook
    9. Project.create() // 运行其自身的 hook (因为全局 hook 被覆盖)
    1. sequelize.addHook('beforeCreate', () => {
    2. // 做些什么
    3. });

    本地 hook 总是在全局 hook 之前运行。

    当您编辑单个对象时,以下 hook 将触发

    1. beforeValidate
    2. afterValidate or validationFailed
    3. beforeCreate / beforeUpdate / beforeDestroy
    4. afterCreate / afterUpdate / afterDestroy
    1. // ...定义 ...
    2. User.beforeCreate(user => {
    3. if (user.accessLevel > 10 && user.username !== "Boss") {
    4. throw new Error("您不能授予该用户10级以上的访问级别!")
    5. }
    6. })

    此示例将返回错误:

    1. User.create({username: 'Not a Boss', accessLevel: 20}).catch(err => {
    2. console.log(err); // 您不能授予该用户 10 级以上的访问级别!
    3. });

    以下示例将返回成功:

    1. User.create({username: 'Boss', accessLevel: 20}).then(user => {
    2. console.log(user); // 用户名为 Boss 和 accessLevel 为 20 的用户对象
    3. });

    有时,您将一次编辑多个记录,方法是使用模型上的 bulkCreate, update, destroy 方法。 当您使用以下方法之一时,将会触发以下内容:

    1. beforeBulkCreate(instances, options)
    2. beforeBulkUpdate(options)
    3. beforeBulkDestroy(options)
    4. afterBulkCreate(instances, options)
    5. afterBulkUpdate(options)
    6. afterBulkDestroy(options)

    如果要为每个单独的记录触发 hook,连同批量 hook,您可以将 personalHooks:true 传递给调用。

    Hook 方法的 options 参数将是提供给相应方法或其克隆和扩展版本的第二个参数。

    1. Model.beforeBulkCreate((records, {fields}) => {
    2. // records = 第一个参数发送到 .bulkCreate
    3. // fields = 第二个参数字段之一发送到 .bulkCreate
    4. })
    5. Model.bulkCreate([
    6. {username: 'Toni'}, // 部分记录参数
    7. {username: 'Tobi'} // 部分记录参数
    8. ], {fields: ['username']} // 选项参数
    9. )
    10. Model.beforeBulkUpdate(({attributes, where}) => {
    11. // where - 第二个参数的克隆的字段之一发送到 .update
    12. // attributes - .update 的第二个参数的克隆的字段之一被用于扩展
    13. })
    14. Model.update({gender: 'Male'} /*属性参数*/, { where: {username: 'Tom'}} /*where 参数*/)
    15. Model.beforeBulkDestroy(({where, individualHooks}) => {
    16. // individualHooks - 第二个参数被扩展的克隆被覆盖的默认值发送到 Model.destroy
    17. // where - 第二个参数的克隆的字段之一发送到 Model.destroy
    18. })
    19. Model.destroy({ where: {username: 'Tom'}} /*where 参数*/)
    1. // 使用 updatesOnDuplicate 选项批量更新现有用户
    2. Users.bulkCreate([
    3. { id: 1, isMember: true },
    4. ], {
    5. updatesOnDuplicate: ['isMember']
    6. });
    7. User.beforeBulkCreate((users, options) => {
    8. for (const user of users) {
    9. if (user.isMember) {
    10. user.memberSince = new Date();
    11. }
    12. }
    13. // 添加 memberSince 到 updatesOnDuplicate 否则 memberSince 期将不会被保存到数据库
    14. options.updatesOnDuplicate.push('memberSince');
    15. });

    在大多数情况下,hook 对于相关联的实例而言将是一样的,除了几件事情之外。

    1. 当使用 add/set 函数时,将运行 beforeUpdate/afterUpdate hook。
    2. 调用 beforeDestroy/afterDestroy hook 的唯一方法是与 onDelete:'cascade 和参数 hooks:true 相关联。 例如:
    1. const Projects = sequelize.define('projects', {
    2. title: DataTypes.STRING
    3. });
    4. const Tasks = sequelize.define('tasks', {
    5. title: DataTypes.STRING
    6. });
    7. Projects.hasMany(Tasks, { onDelete: 'cascade', hooks: true });
    8. Tasks.belongsTo(Projects);

    该代码将在Tasks表上运行beforeDestroy / afterDestroy。 默认情况下,Sequelize会尝试尽可能优化您的查询。 在删除时调用级联,Sequelize将简单地执行一个

    1. DELETE FROM `table` WHERE associatedIdentifier = associatedIdentifier.primaryKey

    然而,添加 hooks: true 会明确告诉 Sequelize,优化不是你所关心的,并且会在关联的对象上执行一个 SELECT,并逐个删除每个实例,以便能够使用正确的参数调用 hook。

    如果您的关联类型为 n:m,则在使用 remove 调用时,您可能有兴趣在直通模型上触发 hook。 在内部,sequelize 使用 Model.destroy,致使在每个实例上调用 bulkDestroy 而不是 before / afterDestroy hook。

    这可以通过将 {individualHooks:true} 传递给 remove 调用来简单地解决,从而导致每个 hook 都通过实例对象被删除。

    关于事务的注意事项

    请注意,Sequelize 中的许多模型操作允许您在方法的 options 参数中指定事务。 如果在原始调用中 指定 了一个事务,它将出现在传递给 hook 函数的 options 参数中。 例如,请参考以下代码段:

    1. // 这里我们使用异步 hook 的 promise 风格,而不是回调。
    2. User.hook('afterCreate', (user, options) => {
    3. // 'transaction' 将在 options.transaction 中可用
    4. // 此操作将成为与原始 User.create 调用相同的事务的一部分。
    5. return User.update({
    6. mood: 'sad'
    7. }, {
    8. where: {
    9. id: user.id
    10. },
    11. transaction: options.transaction
    12. });
    13. });
    14. sequelize.transaction(transaction => {
    15. User.create({
    16. username: 'someguy',
    17. mood: 'happy',
    18. transaction
    19. });

    如果我们在上述代码中的 User.update 调用中未包含事务选项,则不会发生任何更改,因为在已提交挂起的事务之前,我们新创建的用户不存在于数据库中。

    如果在处理操作的过程中已经调用了该 hook ,则这将确保您的依赖读/写是同一事务的一部分。 如果 hook 没有被处理,你只需要指定{ transaction: null } 并且可以预期默认行为。