Sequelize 中文API文档-8. 钩子函数的使用、Hooks相关API

 2016年05月26日    1236     声明


Hooks(钩子)是指会在模型生命周期的特殊时刻被调用的函数,如:模型实例被创建前会调用beforeCreate函数,而在模型实例创建后又会调用afterCreate函数。通过这些钩子函数,使用我们具有在生命周期的特殊时刻访问或操作模型数据的能力。


  1. 钩子函数的使用
  2. Hooks API

1. 钩子函数的使用

钩子函数也被称做回调或生命周期事件,这些函数会在Sequlize 执行一些操作之前或之后被调用。如,你想在模型数据保存前设置一个值,那么就可以添加一个beforeUpdate钩子。

1.1 钩子函数的调用顺序

(1)
  beforeBulkCreate(instances, options, fn)
  beforeBulkDestroy(options, fn)
  beforeBulkUpdate(options, fn)
(2)
  beforeValidate(instance, options, fn)
(-)
  validate
(3)
  afterValidate(instance, options, fn)
  - or -
  validationFailed(instance, options, error, fn)
(4)
  beforeCreate(instance, options, fn)
  beforeDestroy(instance, options, fn)
  beforeUpdate(instance, options, fn)
(-)
  create
  destroy
  update
(5)
  afterCreate(instance, options, fn)
  afterDestroy(instance, options, fn)
  afterUpdate(instance, options, fn)
(6)
  afterBulkCreate(instances, options, fn)
  afterBulkDestroy(options, fn)
  afterBulkUpdate(options, fn)


1.2 定义钩子

钩子的参数通过引用传递,这意味着,你可以修改值且修改会体现在 insert / update 语句中。钩子函数中可以包含异步操作,这时钩子函数中应该返回一个promise

可以通过以下三种方式添加钩子:

// 方法 1, 在.define() 方法中定义
var User = sequelize.define('user', {
  username: DataTypes.STRING,
  mood: {
    type: DataTypes.ENUM,
    values: ['happy', 'sad', 'neutral']
  }
}, {
  hooks: {
    beforeValidate: function(user, options) {
      user.mood = 'happy'
    },
    afterValidate: function(user, options) {
      user.username = 'Toni'
    }
  }
})

// 方法 2, 通过模型的 .hook() 方法定义
User.hook('beforeValidate', function(user, options) {
  user.mood = 'happy'
})

User.hook('afterValidate', function(user, options) {
  return sequelize.Promise.reject("I'm afraid I can't let you do that!")
})

// 方法 3, 通过钩子方法定义
User.beforeCreate(function(user, options) {
  return hashPassword(user.password).then(function (hashedPw) {
    user.password = hashedPw;
  });
})

User.afterValidate('myHookAfter', function(user, options, fn) {
  user.username = 'Toni'
})


1.3 移除钩子

只有有名称的钩子函数才能被称除:

var Book = sequelize.define('book', {
  title: DataTypes.STRING
})

Book.addHook('afterCreate', 'notifyUsers', function(book, options) {
  // ...
})

Book.removeHook('afterCreate', 'notifyUsers')


1.4 全局/普通钩子

全局钩子

全局钩子会在所有的模型中运行,它们可以定义你模型的形为,这些钩子很适合应用一些有用的插件。全局钩子有两种定义方式:

Sequelize.options.define (default hook)

var sequelize = new Sequelize(..., {
  define: {
    hooks: {
      beforeCreate: function () {
        // Do stuff
      }
    }
  }
});

这样就为所有模型定义一个beforeCreate钩子,它会在所有模型被执行create操作前执行:

var User = sequelize.define('user');
var Project = sequelize.define('project', {}, {
    hooks: {
        beforeCreate: function () {
            // Do other stuff
        }
    }
});

User.create() // 执行全局钩子
Project.create() // 执行自已的钩子,因为全局钩子会被重写

Sequelize.addHook(permanent hook)

sequelize.addHook('beforeCreate', function () {
    // Do stuff
});

通过addHook方法添加的钩子总是会执行,不会被模型本身定义的钩子函数所覆盖:

var User = sequelize.define('user'); var Project = sequelize.define('project', {}, { hooks: { beforeCreate: function () { // Do other stuff } } }); User.create() // 执行全局钩子 Project.create() // 先执行自己的钩子,再执行全局钩子

本地钩子总是会在全局钩子之前执行

实例钩子

单个实例中也有一些钩子,它们会在你编辑修改实例时被执行:

beforeValidate
afterValidate or validationFailed
beforeCreate / beforeUpdate  / beforeDestroy
afterCreate / afterUpdate / afterDestroy
// 定义
User.beforeCreate(function(user) {
  if (user.accessLevel > 10 && user.username !== "Boss") {
    throw new Error("该问该用户需要10 级以上权限!")
  }
})

这个示例中,通过钩子返回一个错误:

User.create({username: 'Not a Boss', accessLevel: 20}).catch(function(err) {
  console.log(err) // 该问该用户需要10 级以上权限!
})

操作成功:

User.create({username: 'Boss', accessLevel: 20}).then(function(user) {
  console.log(user) // user object with username as Boss and accessLevel of 20
})

模型钩子

有时我们需要在模型创建/更新/删除等特殊时刻对记录做一些修改,这些可以使用模型钩子。可于模型的钩子有:

beforeBulkCreate / beforeBulkUpdate / beforeBulkDestroy
afterBulkCreate / afterBulkUpdate / afterBulkDestroy

在匹量操作中,如果你想把钩子应用到每个单独的记录中,请设置individualHooks: true选项:

Model.destroy({ where: {accessLevel: 0}, individualHooks: true})
// 会在每条被删除记录执行删除操作前/后 调用钩子

Model.update({username: 'Toni'}, { where: {accessLevel: 0}, individualHooks: true})
// 会在每条被更新记录执行更新操作前/后 调用钩子

实例钩子可能会有两个或三个参数,这取决于钩子的类型:

Model.beforeBulkCreate(function(records, fields) {
  // records 是发送给 .bulkCreate的第一个参数
  // fields 是发送给 .bulkCreate的第二个参数
})

Model.bulkCreate([
  {username: 'Toni'}, // records 参数部分
  {username: 'Tobi'} // records 参数部分
], ['username'] /* fields 参数部分 */)

Model.beforeBulkUpdate(function(attributes, where) {
  // attributes 是发送 Model.update 的第一个参数
  // where 是发送 Model.update 的第二个参数
})

Model.update({gender: 'Male'} /*attributes argument*/, { where: {username: 'Tom'}} /*where argument*/)

Model.beforeBulkDestroy(function(whereClause) {
  // whereClause 是发送给 Model.destroy 的第一个参数
})

Model.destroy({ where: {username: 'Tom'}} /*whereClause 参数*/)

Model.bulkCreate(...)方法中使用updatesOnDuplicate选项时,我们可以在钩子函数中对updatesOnDuplicate数据进行修改:

// 使用updatesOnDuplicate 选项在用户存在时进行批量更新
Users.bulkCreate([{ id: 1, isMemeber: true}, 
                 { id: 2, isMember: false}], 
                 { updatesOnDuplicate: ['isMember']})

User.beforeBulkCreate(function (users, options) {
  users.forEach(function (user) {
    if (user.isMember) {
      user.memberSince = new Date()
    }
  })

  // 为 updatesOnDuplicate 添加一个 memberSince,其它情况下 memberSince 数据不会保存到数据库
  options.updatesOnDuplicate.push('memberSince')


1.5 关联模型中的钩子

大部分钩在模型关联中同样可以使用:

  1. 当使用 add/set 函数时,beforeUpdate/afterUpdate 钩子会执行
  2. 只有一种方法可以调用 beforeDestroy/afterDestroy 钩子,那就是在关联中使用 onDelete: 'cascade'hooks: true选项。如:
var Projects = sequelize.define('projects', {
  title: DataTypes.STRING
})

var Tasks = sequelize.define('tasks', {
  title: DataTypes.STRING
})

Projects.hasMany(Tasks, { onDelete: 'cascade', hooks: true })
Tasks.belongsTo(Projects)

这段代码会在删除Tasks表数据时,调用 beforeDestroy/afterDestroy 钩子。默认情况下,Sequelize会尝试尽可能优化你的查询语句,在连接删除中会像下面这样简单的执行:

DELETE FROM `table` WHERE associatedIdentifier = associatedIdentifier.primaryKey

添加hooks: true后,相当于告诉Sequelize 你并不关心优化,这将会在每个实例删除时分别应用钩子。

n:m关联中,你可能想在调用remove时使用钩子,这时Sequelize 会使用Model.destroy代替bulkDestroy,以分别为每个实例应用before/afterDestroy钩子。

这时,可以通过在remove方法中添加{individualHooks: true}选项以分别为每个实例就用钩子。


1.6 在事务中应注意

在操作多个模型时,可以设置transaction选项,以保障操作数据的完整性。如果在原调用中指定了一个事务,那它将在传递给钩子函数的选项参数中出现:

// 这时使用 promise 式的异步钩子而非回调
User.hook('afterCreate', function(user, options) {
  // 'transaction' 通过 options.transaction 设置

  // 此操作会将相同事务部分像原始 User.create一样调用
  return User.update({
    mood: 'sad'
  }, {
    where: {
      id: user.id
    },
    transaction: options.transaction
  });
});


sequelize.transaction(function(t) {
  User.create({
    username: 'someguy',
    mood: 'happy',
    transaction: t
  });
});

如果在调用User.update时没有设置事务选项,那么不会做任何操作,直到后面的create操作发生时才会触发。


2. Hooks API

2.1 相关说明

钩子是会在(批量-)创建/更新/删除和验证之前和之后调用的函数。钩子可以通过以下三种方式添加:

  1. 在模型定义方法 sequelize.define 的选项中指定
  2. 通过调用 hook() 并传入钩子函数名指事实上
  3. 在模型上直接调用钩子函数


2.2 addHook() - 添加钩子

addHook(hooktype, [name], fn)

为模型添加钩子

  • hooktype - {String},钩子类型
  • [name] - {String},钩子名,可用于以后钩子函数的移除
  • fn - {Function},要添加的钩子函数

别名:hook


2.3 removeHook() - 移除钩子

removeHook(hookType, name)

从模型中移除钩子

  • hooktype - {String},钩子类型
  • name - {String},钩子名


2.4 hasHook() - 是否有钩子

hasHook(hookType)

检查模型中是否存在指定类型的钩子

  • hooktype - {String},钩子类型

别名:hasHooks


2.5 beforeValidate() - 验证前执行的钩子

beforeValidate(name, fn)

用于在模型执行数据验证前执行的钩子函数

  • name - {String}
  • fn - {Function},要执行的钩子函数


2.6 afterValidate() - 验证后执行的钩子

afterValidate(name, fn)

用于在模型数据验证完成后执行的钩子函数

  • name - {String}
  • fn - {Function},要执行的钩子函数


2.7 afterValidate() - 创建前执行的钩子

beforeCreate(name, fn)

用于在模型创建实例前执行的钩子函数

  • name - {String}
  • fn - {Function},要执行的钩子函数


2.8 afterValidate() - 创建后执行的钩子

beforeDestroy(name, fn)

用于在模型创建实例后执行的钩子函数

  • name - {String}
  • fn - {Function},要执行的钩子函数


2.9 beforeDestroy() - 删除前执行的钩子

beforeDestroy(name, fn)

用于在模型删除实例前执行的钩子函数

  • name - {String}
  • fn - {Function},要执行的钩子函数

别名:beforeDelete


2.10 afterDestroy() - 删除后执行的钩子

afterDestroy(name, fn)

用于在模型删除实例后执行的钩子函数

  • name - {String}
  • fn - {Function},要执行的钩子函数

别名:afterDestroy


2.11 beforeRestore() - 恢复前执行的钩子

beforeRestore(name, fn)

用于在模型实例恢复(软删除)前执行的钩子函数

  • name - {String}
  • fn - {Function},要执行的钩子函数


2.12 afterRestore() - 恢复后执行的钩子

afterRestore(name, fn)

用于在模型实例恢复(软删除)后执行的钩子函数

  • name - {String}
  • fn - {Function},要执行的钩子函数


2.13 beforeUpdate() - 更新前执行的钩子

beforeUpdate(name, fn)

用于在模型实例更新前执行的钩子函数

  • name - {String}
  • fn - {Function},要执行的钩子函数


2.14 afterUpdate() - 更新后执行的钩子

afterUpdate(name, fn)

用于在模型实例更新后执行的钩子函数

  • name - {String}
  • fn - {Function},要执行的钩子函数


2.15 beforeBulkCreate() - 批量创建前执行的钩子

beforeBulkCreate(name, fn)

用于在批量创建模型实例前执行的钩子函数

  • name - {String}
  • fn - {Function},要执行的钩子函数


2.16 afterBulkCreate() - 批量创建后执行的钩子

afterBulkCreate(name, fn)

用于在批量创建模型实例后执行的钩子函数

  • name - {String}
  • fn - {Function},要执行的钩子函数


2.17 beforeBulkDestroy() - 批量删除前执行的钩子

beforeBulkDestroy(name, fn)

用于在批量删除模型实例前执行的钩子函数

  • name - {String}
  • fn - {Function},要执行的钩子函数

别名:beforeBulkDelete


2.18 afterBulkDestroy() - 批量删除后执行的钩子

afterBulkDestroy(name, fn)

用于在批量删除模型实例后执行的钩子函数

  • name - {String}
  • fn - {Function},要执行的钩子函数

别名:afterBulkDelete


2.19 beforeBulkRestore() - 批量恢复前执行的钩子

beforeBulkRestore(name, fn)

用于在批量恢复模型实例前执行的钩子函数

  • name - {String}
  • fn - {Function},要执行的钩子函数


2.20 afterBulkRestore() - 批量恢复后执行的钩子

afterBulkRestore(name, fn)

用于在批量恢复模型实例后执行的钩子函数

  • name - {String}
  • fn - {Function},要执行的钩子函数


2.21 beforeBulkUpdate() - 批量更新前执行的钩子

beforeBulkUpdate(name, fn)

用于在批量更新模型实例前执行的钩子函数

  • name - {String}
  • fn - {Function},要执行的钩子函数


2.22 afterBulkUpdate() - 批量更新后执行的钩子

afterBulkUpdate(name, fn)

用于在批量更新模型实例后执行的钩子函数

  • name - {String}
  • fn - {Function},要执行的钩子函数


2.23 beforeFind() - 查询前执行的钩子

beforeFind(name, fn)

用于在查找模型数据民前执行的钩子函数

  • name - {String}
  • fn - {Function},要执行的钩子函数


2.24 beforeFindAfterExpandIncludeAll() - 查询前展开后执行的钩子

beforeFindAfterExpandIncludeAll(name, fn)

用于在执行find()查询前, { include: {all: ...} }展开后执行的钩子函数

  • name - {String}
  • fn - {Function},要执行的钩子函数


2.25 beforeFindAfterOptions() - 查询前解析后执行的钩子

beforeFindAfterOptions(name, fn)

用于在执行find()查询前,选项参数解析守成后执行的钩子函数

  • name - {String}
  • fn - {Function},要执行的钩子函数


2.26 afterFind() - 查询后执行的钩子

afterFind(name, fn)

用于在查询完成后执行的钩子函数

  • name - {String}
  • fn - {Function},要执行的钩子函数


2.27 beforeDefine() - 定义前执行的钩子

beforeDefine(name, fn)

用于在调用define前执行的钩子函数

  • name - {String}
  • fn - {Function},要执行的钩子函数


2.28 afterDefine() - 定义后执行的钩子

afterDefine(name, fn)

用于在调用define后执行的钩子函数

  • name - {String}
  • fn - {Function},要执行的钩子函数


2.29 beforeInit() - 初始化前执行的钩子

beforeInit(name, fn)

用于在调用Sequelize()前执行的钩子函数

  • name - {String}
  • fn - {Function},要执行的钩子函数


2.30 afterInit() - 初始化后执行的钩子

afterInit(name, fn)

用于在调用Sequelize()后执行的钩子函数

  • name - {String}
  • fn - {Function},要执行的钩子函数


2.31 beforeSync() - 同步前执行的钩子

beforeSync(name, fn)

用于在调用Model.sync()前执行的钩子函数

  • name - {String}
  • fn - {Function},要执行的钩子函数


2.32 afterSync() - 同步后执行的钩子

afterSync(name, fn)

用于在调用Model.sync()后执行的钩子函数

  • name - {String}
  • fn - {Function},要执行的钩子函数


2.33 beforeBulkSync() - 批量同步前执行的钩子

beforeBulkSync(name, fn)

用于在调用sequelize.sync()前执行的钩子函数

  • name - {String}
  • fn - {Function},要执行的钩子函数


2.34 afterBulkSync() - 批量同步后执行的钩子

afterBulkSync(name, fn)

用于在调用sequelize.sync()后执行的钩子函数

  • name - {String}
  • fn - {Function},要执行的钩子函数