sequelize 钩子——迹忆客-ag捕鱼王app官网
hooks(也称为生命周期事件)是在执行 sequelize 中的调用之前和之后调用的函数。 例如,如果要在保存之前始终在模型上设置一个值,则可以添加一个 beforeupdate hook。
注意
: 我们不能对实例使用 hook。 hook 用于模型。
hooks 触发顺序
下面显示了最常见的 hook 的触发顺序。
注意: 此列表并不详尽。
(1)
beforebulkcreate(instances, options)
beforebulkdestroy(options)
beforebulkupdate(options)
(2)
beforevalidate(instance, options)
[... validation happens ...]
(3)
aftervalidate(instance, options)
validationfailed(instance, options, error)
(4)
beforecreate(instance, options)
beforedestroy(instance, options)
beforeupdate(instance, options)
beforesave(instance, options)
beforeupsert(values, options)
[... creation/update/destruction happens ...]
(5)
aftercreate(instance, options)
afterdestroy(instance, options)
afterupdate(instance, options)
aftersave(instance, options)
afterupsert(created, options)
(6)
afterbulkcreate(instances, options)
afterbulkdestroy(options)
afterbulkupdate(options)
声明 hooks
hook 的参数通过引用传递。 这意味着你可以更改值,这将反映在 insert / update
语句中。 一个 hook 可能包含异步动作 - 在这种情况下,hook 函数应该返回一个 promise。
当前有三种方法以编程方式添加 hook:
// 方法 1 通过 .init() 方法
class user extends model {}
user.init({
username: datatypes.string,
mood: {
type: datatypes.enum,
values: ['happy', 'sad', 'neutral']
}
}, {
hooks: {
beforevalidate: (user, options) => {
user.mood = 'happy';
},
aftervalidate: (user, options) => {
user.username = 'toni';
}
},
sequelize
});
// 方法 2 通过 .addhook() 方法
user.addhook('beforevalidate', (user, options) => {
user.mood = 'happy';
});
user.addhook('aftervalidate', 'somecustomname', (user, options) => {
return promise.reject(new error("i'm afraid i can't let you do that!"));
});
// 方法 3 通过 direct 方法
user.beforecreate(async (user, options) => {
const hashedpassword = await hashpassword(user.password);
user.password = hashedpassword;
});
user.aftervalidate('myhookafter', (user, options) => {
user.username = 'toni';
});
删除 hooks
只能删除带有名称参数的 hook。
class book extends model {}
book.init({
title: datatypes.string
}, { sequelize });
book.addhook('aftercreate', 'notifyusers', (book, options) => {
// ...
});
book.removehook('aftercreate', 'notifyusers');
你可以有许多同名的 hook。 调用 .removehook()
将删除所有对象。
全局 / 通用 hooks
全局 hook 是所有模型运行的 hook。 它们对于插件特别有用, 并且可以为所有模型定义您想要的行为。 例如允许在您的模型上使用 sequelize.define
自定义时间戳:
const user = sequelize.define('user', {}, {
tablename: 'users',
hooks : {
beforecreate : (record, options) => {
record.datavalues.createdat = new date().toisostring().replace(/t/, ' ').replace(/\.. /g, '');
record.datavalues.updatedat = new date().toisostring().replace(/t/, ' ').replace(/\.. /g, '');
},
beforeupdate : (record, options) => {
record.datavalues.updatedat = new date().toisostring().replace(/t/, ' ').replace(/\.. /g, '');
}
}
});
它们可以通过多种方式定义, 语义略有不同:
默认 hooks (在 sequelize 构造函数参数)
const sequelize = new sequelize(..., {
define: {
hooks: {
beforecreate() {
// 做点什么
}
}
}
});
这会向所有模型添加一个默认 hook,如果模型未定义自己的 beforecreate hook,则将运行该 hook:
const user = sequelize.define('user', {});
const project = sequelize.define('project', {}, {
hooks: {
beforecreate() {
// 做点其他事
}
}
});
await user.create({}); // 运行全局 hook
await project.create({}); // 运行自己的 hook (因为全局 hook 被覆盖)
常驻 hooks (通过 sequelize.addhook)
sequelize.addhook('beforecreate', () => {
// 做点什么
});
无论模型是否指定自己的 beforecreate hook,该 hook 始终运行。 本地 hook 总是在全局 hook 之前运行:
const user = sequelize.define('user', {});
const project = sequelize.define('project', {}, {
hooks: {
beforecreate() {
// 做点其他事
}
}
});
await user.create({}); // 运行全局 hook
await project.create({}); // 运行自己的 hook, 其次是全局 hook
也可以在传递给 sequelize 构造函数的参数中定义常驻 hook:
new sequelize(..., {
hooks: {
beforecreate() {
// 做点什么
}
}
});
请注意,以上内容与上述 默认 hooks 不同。 那就是使用构造函数的 define 参数。 这里不是。
连接 hooks
sequelize 提供了四个 hook,它们在获得或释放数据库连接之前和之后立即执行:
- sequelize.beforeconnect(callback)
- 回调具有以下形式 async (config) => /* ... */
- sequelize.afterconnect(callback)
- 回调具有以下形式 async (connection, config) => /* ... */
- sequelize.beforedisconnect(callback)
- 回调具有以下形式 async (connection) => /* ... */
- sequelize.afterdisconnect(callback)
- 回调具有以下形式 async (connection) => /* ... */
如果你需要异步获取数据库凭据,或者需要在创建低级数据库连接后直接访问它,这些 hook 很有用。
例如,我们可以从令牌存储异步获取数据库密码,并使用新的凭证对 sequelize 的配置对象进行更新:
sequelize.beforeconnect(async (config) => {
config.password = await getauthtoken();
});
这些 hook 只能 被 声明为永久全局 hook,因为所有模型都共享连接池。
实例 hooks
每当你编辑单个对象时,都会触发以下 hook:
- beforevalidate
- aftervalidate / validationfailed
- beforecreate / beforeupdate / beforesave / beforedestroy
- aftercreate / afterupdate / aftersave / afterdestroy
user.beforecreate(user => {
if (user.accesslevel > 10 && user.username !== "boss") {
throw new error("you can't grant this user an access level above 10!");
}
});
以下示例将引发错误:
try {
await user.create({ username: 'not a boss', accesslevel: 20 });
} catch (error) {
console.log(error); // 你不能授予该用户10以上的访问权限!
};
以下示例将成功:
const user = await user.create({ username: 'boss', accesslevel: 20 });
console.log(user); // 用户对象,用户名 `boss`,accesslevel 为 20
模型 hooks
有时你会使用诸如 bulkcreate, update 和 destroy 之类的方法一次编辑多个记录。 每当你使用这些方法之一时,就会触发以下 hook:
- yourmodel.beforebulkcreate(callback)
- 回调具有以下形式 (instances, options) => /* ... */
- yourmodel.beforebulkupdate(callback)
- 回调具有以下形式 (options) => /* ... */
- yourmodel.beforebulkdestroy(callback)
- 回调具有以下形式 (options) => /* ... */
- yourmodel.afterbulkcreate(callback)
- 回调具有以下形式 (instances, options) => /* ... */
- yourmodel.afterbulkupdate(callback)
- 回调具有以下形式 (options) => /* ... */
- yourmodel.afterbulkdestroy(callback)
- 回调具有以下形式 (options) => /* ... */
注意:默认情况下,类似 bulkcreate 的方法不会触发单独的 hook - 仅批量 hook。 但是,如果你还希望触发单个 hook,则可以将 { individualhooks: true }
参数传递给查询调用。 但是,这可能会严重影响性能,具体取决于所涉及的记录数(因为,除其他外,所有实例都将被加载到内存中)。 例子:
await model.destroy({
where: { accesslevel: 0 },
individualhooks: true
});
// 这将选择所有将要删除的记录,并在每个实例上触发 `beforedestroy` 和 `afterdestroy`.
await model.update({ username: 'tony' }, {
where: { accesslevel: 0 },
individualhooks: true
});
// 这将选择所有将要更新的记录,并在每个实例上发出 `beforeupdate` 和 `afterupdate`.
如果你将 model.bulkcreate(...)
与 updateonduplicate
参数一起使用,则对 hook 中对 updateonduplicate 数组中未提供的字段所做的更改将不会保留到数据库中。 但是,如果需要的话,可以在挂钩中更改 updateonduplicate 参数。
user.beforebulkcreate((users, options) => {
for (const user of users) {
if (user.ismember) {
user.membersince = new date();
}
}
// 将 `membersince` 添加到 updateonduplicate,否则它将不会持久化
if (options.updateonduplicate && !options.updateonduplicate.includes('membersince')) {
options.updateonduplicate.push('membersince');
}
});
// 使用 updateonduplicate 参数批量更新现有用户
await users.bulkcreate([
{ id: 1, ismember: true },
{ id: 2, ismember: false }
], {
updateonduplicate: ['ismember']
});
关联
在大多数情况下,hook 在关联时对实例的作用相同。
一对一和一对多关联
当使用 add/set mixin 方法时,beforeupdate 和 afterupdate hook 将运行。
beforedestroy 和 afterdestroy hook 只会在具有 ondelete: 'cascade'
和 hooks: true
的关联上被调用。 例如
class projects extends model {}
projects.init({
title: datatypes.string
}, { sequelize });
class tasks extends model {}
tasks.init({
title: datatypes.string
}, { sequelize });
projects.hasmany(tasks, { ondelete: 'cascade', hooks: true });
tasks.belongsto(projects);
该代码将在 tasks 模型上运行 beforedestroy 和 afterdestroy hook。
默认情况下,sequelize 将尝试尽可能优化你的查询。 当在删除时调用级联时,sequelize 将简单地执行:
delete from `table` where associatedidentifier = associatedidentifier.primarykey
但是,添加 hooks: true
会明确告诉 sequelize 优化与你无关。 然后,sequelize 首先将对关联的对象执行 select 并逐个销毁每个实例,以便能够正确调用 hook(使用正确的参数)。
多对多关联
- 当对 belongstomany 关系使用 add mixin 方法时(将一个或多个记录添加到联结表中),联结模型中的 beforebulkcreate 和 afterbulkcreate hook 将运行。
- 如果将
{ individualhooks: true }
传递给该调用,则每个单独的 hook 也将运行。
- 如果将
- 当对 belongstomany 关系使用 remove mixin 方法时(将一个或多个记录删除到联结表中),联结模型中的 beforebulkdestroy 和 afterbulkdestroy hook 将运行。
- 如果将
{ individualhooks: true }
传递给该调用,则每个单独的 hook 也将运行。
- 如果将
如果你的关联是多对多,则在使用 remove 调用时,你可能会对在直通模型上触发 hook 感兴趣。 在内部,sequelize 使用的是 model.destroy
,从而导致在每个直通实例上调用 bulkdestroy 而不是 before/afterdestroy hook。
hooks 和 事务
sequelize 中的许多模型操作都允许你在方法的 options 参数中指定事务。 如果在原始调用中指定了事务,则该事务将出现在传递给 hook 函数的 options 参数中。 例如,考虑以下代码片段:
user.addhook('aftercreate', async (user, options) => {
// 我们可以使用 `options.transaction` 来执行
// 与触发此 hook 的调用相同的事务来执行其他一些调用
await user.update({ mood: 'sad' }, {
where: {
id: user.id
},
transaction: options.transaction
});
});
await sequelize.transaction(async t => {
await user.create({
username: 'someguy',
mood: 'happy'
}, {
transaction: t
});
});
如果在前面的代码中对 user.update
的调用中未包含 transaction 参数,则不会发生更改,因为在提交未决事务之前,数据库中不存在我们新创建的用户。
内部事务
重要的是要认识到,sequelize 可能会在内部对某些操作(例如model.findorcreate)使用事务. 如果 hook 函数执行依赖于数据库中对象存在的读取或写入操作,或者像上一节中的示例一样修改对象的存储值,则应始终指定{transaction:options.transaction}:
- 如果使用了事务,则
{transaction:options.transaction}
将确保再次使用该事务; - 否则,
{ transaction: options.transaction }
等同于{ transaction: undefined }
,这将不使用事务。
这样,你的 hook 将始终正确运行。