8. 钩子函数的使用、Hooks相关API
Hooks
(钩子)是指会在模型生命周期的特殊时刻被调用的函数,如:模型实例被创建前会调用beforeCreate
函数,而在模型实例创建后又会调用afterCreate
函数。通过这些钩子函数,使用我们具有在生命周期的特殊时刻访问或操作模型数据的能力。
- 钩子函数的使用
- 1.1 钩子函数的调用顺序
- 1.2 定义钩子
- 1.3 移除钩子
- 1.4 全局/普通钩子
- 1.5 关联模型中的钩子
- 1.6 在事务中应注意
- Hooks API
- 2.1 相关说明
- 2.2
addHook()
- 添加钩子 - 2.3
removeHook()
- 移除钩子 - 2.4
hasHook()
- 是否有钩子 - 2.5
beforeValidate()
- 验证前执行的钩子 - 2.6
afterValidate()
- 验证后执行的钩子 - 2.7
afterValidate()
- 创建前执行的钩子 - 2.8
afterValidate()
- 创建后执行的钩子 - 2.9
beforeDestroy()
- 删除前执行的钩子 - 2.10
afterDestroy()
- 删除后执行的钩子 - 2.11
beforeRestore()
- 恢复前执行的钩子 - 2.12
afterRestore()
- 恢复后执行的钩子 - 2.13
beforeUpdate()
- 更新前执行的钩子 - 2.14
afterUpdate()
- 更新后执行的钩子 - 2.15
beforeBulkCreate()
- 批量创建前执行的钩子 - 2.16
afterBulkCreate()
- 批量创建后执行的钩子 - 2.17
beforeBulkDestroy()
- 批量删除前执行的钩子 - 2.18
afterBulkDestroy()
- 批量删除后执行的钩子 - 2.19
beforeBulkRestore()
- 批量恢复前执行的钩子 - 2.20
afterBulkRestore()
- 批量恢复后执行的钩子 - 2.21
beforeBulkUpdate()
- 批量更新前执行的钩子 - 2.22
afterBulkUpdate()
- 批量更新后执行的钩子 - 2.23
beforeFind()
- 查询前执行的钩子 - 2.24
beforeFindAfterExpandIncludeAll()
- 查询前展开后执行的钩子 - 2.25
beforeFindAfterOptions()
- 查询前解析后执行的钩子 - 2.26
afterFind()
- 查询后执行的钩子 - 2.27
beforeDefine()
- 定义前执行的钩子 - 2.28
afterDefine()
- 定义后执行的钩子 - 2.29
beforeInit()
- 初始化前执行的钩子 - 2.30
afterInit()
- 初始化后执行的钩子 - 2.31
beforeSync()
- 同步前执行的钩子 - 2.32
afterSync()
- 同步后执行的钩子 - 2.33
beforeBulkSync()
- 批量同步前执行的钩子 - 2.34
afterBulkSync()
- 批量同步后执行的钩子
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 关联模型中的钩子
大部分钩在模型关联中同样可以使用:
- 当使用 add/set 函数时,beforeUpdate/afterUpdate 钩子会执行
- 只有一种方法可以调用 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 相关说明
钩子是会在(批量-)创建/更新/删除和验证之前和之后调用的函数。钩子可以通过以下三种方式添加:
- 在模型定义方法
sequelize.define
的选项中指定 - 通过调用
hook()
并传入钩子函数名指事实上 - 在模型上直接调用钩子函数
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},要执行的钩子函数