Hooks – 鉤子
此係列文章的應用示例已釋出於 GitHub: sequelize-docs-Zh-CN. 可以 Fork 幫助改進或 Star 關注更新. 歡迎 Star.
Hook(也稱為生命週期事件)是執行 sequelize 呼叫之前和之後呼叫的函式。 例如,如果要在儲存模型之前始終設定值,可以新增一個 beforeUpdate
hook。
獲取完整列表, 請檢視 Hooks file.
操作清單
(1)
beforeBulkCreate(instances, options)
beforeBulkDestroy(options)
beforeBulkUpdate(options)
(2)
beforeValidate(instance, options)
(-)
validate
(3)
afterValidate(instance, options)
- or -
validationFailed(instance, options, error)
(4)
beforeCreate(instance, options)
beforeDestroy(instance, options)
beforeUpdate(instance, options)
beforeSave(instance, options)
beforeUpsert(values, options)
(-)
create
destroy
update
(5)
afterCreate(instance, options)
afterDestroy(instance, options)
afterUpdate(instance, options)
afterSave(instance, options)
afterUpsert(created, options)
(6)
afterBulkCreate(instances, options)
afterBulkDestroy(options)
afterBulkUpdate(options)
宣告 Hook
Hook 的引數通過引用傳遞。 這意味著您可以更改值,這將反映在insert / update語句中。 Hook 可能包含非同步動作 – 在這種情況下,Hook 函式應該返回一個 promise。
目前有三種以程式設計方式新增 hook 的方法:
// 方法1 通過 .define() 方法
const User = sequelize.define(`user`, {
username: DataTypes.STRING,
mood: {
type: DataTypes.ENUM,
values: [`happy`, `sad`, `neutral`]
}
}, {
hooks: {
beforeValidate: (user, options) => {
user.mood = `happy`;
},
afterValidate: (user, options) => {
user.username = `Toni`;
}
}
});
// 方法2 通過 . hook() 方法 (或其別名 .addHook() 方法)
User.hook(`beforeValidate`, (user, options) => {
user.mood = `happy`;
});
User.addHook(`afterValidate`, `someCustomName`, (user, options) => {
return sequelize.Promise.reject(new Error("I`m afraid I can`t let you do that!"));
});
// 方法3 通過直接方法
User.beforeCreate((user, options) => {
return hashPassword(user.password).then(hashedPw => {
user.password = hashedPw;
});
});
User.afterValidate(`myHookAfter`, (user, options) => {
user.username = `Toni`;
});
移除 Hook
只能刪除有名稱引數的 hook。
const Book = sequelize.define(`book`, {
title: DataTypes.STRING
});
Book.addHook(`afterCreate`, `notifyUsers`, (book, options) => {
// ...
});
Book.removeHook(`afterCreate`, `notifyUsers`);
你可以有很多同名的 hook。 呼叫 .removeHook()
將會刪除它們。
全域性 / 通用 Hook
全域性 hook 是所有模型的 hook。 他們可以定義您想要的所有模型的行為,並且對外掛特別有用。 它們可以用兩種方式來定義,它們的語義略有不同:
Sequelize.options.define (預設 hook)
const sequelize = new Sequelize(..., {
define: {
hooks: {
beforeCreate: () => {
// 做些什麼
}
}
}
});
這將為所有模型新增一個預設 hook,如果模型沒有定義自己的 beforeCreate
hook,那麼它將執行。
const User = sequelize.define(`user`);
const Project = sequelize.define(`project`, {}, {
hooks: {
beforeCreate: () => {
// 做些其它什麼
}
}
});
User.create() // 執行全域性 hook
Project.create() // 執行其自身的 hook (因為全域性 hook 被覆蓋)
Sequelize.addHook (常駐 hook)
sequelize.addHook(`beforeCreate`, () => {
// 做些什麼
});
這個 hook 總是在建立之前執行,無論模型是否指定了自己的 beforeCreate
hook:
const User = sequelize.define(`user`);
const Project = sequelize.define(`project`, {}, {
hooks: {
beforeCreate: () => {
// 做些其它什麼
}
}
});
User.create() // 執行全域性 hook
Project.create() //執行其自己的 hook 之後執行全域性 hook
本地 hook 總是在全域性 hook 之前執行。
例項 Hook
當您編輯單個物件時,以下 hook 將觸發
beforeValidate
afterValidate or validationFailed
beforeCreate / beforeUpdate / beforeDestroy
afterCreate / afterUpdate / afterDestroy
// ...定義 ...
User.beforeCreate(user => {
if (user.accessLevel > 10 && user.username !== "Boss") {
throw new Error("您不能授予該使用者10級以上的訪問級別!")
}
})
此示例將返回錯誤:
User.create({username: `Not a Boss`, accessLevel: 20}).catch(err => {
console.log(err); // 您不能授予該使用者 10 級以上的訪問級別!
});
以下示例將返回成功:
User.create({username: `Boss`, accessLevel: 20}).then(user => {
console.log(user); // 使用者名稱為 Boss 和 accessLevel 為 20 的使用者物件
});
模型 Hook
有時,您將一次編輯多個記錄,方法是使用模型上的 bulkCreate, update, destroy
方法。 當您使用以下方法之一時,將會觸發以下內容:
beforeBulkCreate(instances, options)
beforeBulkUpdate(options)
beforeBulkDestroy(options)
afterBulkCreate(instances, options)
afterBulkUpdate(options)
afterBulkDestroy(options)
如果要為每個單獨的記錄觸發 hook,連同批量 hook,您可以將 personalHooks:true
傳遞給呼叫。
Model.destroy({ where: {accessLevel: 0}, individualHooks: true});
// 將選擇要刪除的所有記錄,並在每個例項刪除之前 + 之後觸發
Model.update({username: `Toni`}, { where: {accessLevel: 0}, individualHooks: true});
// 將選擇要更新的所有記錄,並在每個例項更新之前 + 之後觸發
Hook 方法的 options
引數將是提供給相應方法或其克隆和擴充套件版本的第二個引數。
Model.beforeBulkCreate((records, {fields}) => {
// records = 第一個引數傳送到 .bulkCreate
// fields = 第二個引數欄位之一傳送到 .bulkCreate
})
Model.bulkCreate([
{username: `Toni`}, // 部分記錄引數
{username: `Tobi`} // 部分記錄引數
], {fields: [`username`]} // 選項引數
)
Model.beforeBulkUpdate(({attributes, where}) => {
// where - 第二個引數的克隆的欄位之一傳送到 .update
// attributes - .update 的第二個引數的克隆的欄位之一被用於擴充套件
})
Model.update({gender: `Male`} /*屬性引數*/, { where: {username: `Tom`}} /*where 引數*/)
Model.beforeBulkDestroy(({where, individualHooks}) => {
// individualHooks - 第二個引數被擴充套件的克隆被覆蓋的預設值傳送到 Model.destroy
// where - 第二個引數的克隆的欄位之一傳送到 Model.destroy
})
Model.destroy({ where: {username: `Tom`}} /*where 引數*/)
如果用 updates.OnDuplicate
引數使用 Model.bulkCreate(...)
,那麼 hook 中對 updatesOnDuplicate
陣列中沒有給出的欄位所做的更改將不會被持久保留到資料庫。 但是,如果這是您想要的,則可以更改 hook 中的 updatesOnDuplicate 選項。
// 使用 updatesOnDuplicate 選項批量更新現有使用者
Users.bulkCreate([
{ id: 1, isMemeber: true },
{ id: 2, isMember: false }
], {
updatesOnDuplicate: [`isMember`]
});
User.beforeBulkCreate((users, options) => {
for (const user of users) {
if (user.isMember) {
user.memberSince = new Date();
}
}
// 新增 memberSince 到 updatesOnDuplicate 否則 memberSince 期將不會被儲存到資料庫
options.updatesOnDuplicate.push(`memberSince`);
});
關聯
在大多數情況下,hook 對於相關聯的例項而言將是一樣的,除了幾件事情之外。
- 當使用 add/set 函式時,將執行 beforeUpdate/afterUpdate hook。
- 呼叫 beforeDestroy/afterDestroy hook 的唯一方法是與
onDelete:`cascade
和引數hooks:true
相關聯。 例如:
const Projects = sequelize.define(`projects`, {
title: DataTypes.STRING
});
const Tasks = sequelize.define(`tasks`, {
title: DataTypes.STRING
});
Projects.hasMany(Tasks, { onDelete: `cascade`, hooks: true });
Tasks.belongsTo(Projects);
該程式碼將在Tasks表上執行beforeDestroy / afterDestroy。 預設情況下,Sequelize會嘗試儘可能優化您的查詢。 在刪除時呼叫級聯,Sequelize將簡單地執行一個
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 引數中。 例如,請參考以下程式碼段:
// 這裡我們使用非同步 hook 的 promise 風格,而不是回撥。
User.hook(`afterCreate`, (user, options) => {
// `transaction` 將在 options.transaction 中可用
// 此操作將成為與原始 User.create 呼叫相同的事務的一部分。
return User.update({
mood: `sad`
}, {
where: {
id: user.id
},
transaction: options.transaction
});
});
sequelize.transaction(transaction => {
User.create({
username: `someguy`,
mood: `happy`,
transaction
});
});
如果我們在上述程式碼中的 User.update
呼叫中未包含事務選項,則不會發生任何更改,因為在已提交掛起的事務之前,我們新建立的使用者不存在於資料庫中。
內部事務
要認識到 sequelize 可能會在某些操作(如 Model.findOrCreate
)內部使用事務是非常重要的。 如果你的 hook 函式執行依賴物件在資料庫中存在的讀取或寫入操作,或者修改物件的儲存值,就像上一節中的例子一樣,你應該總是指定 { transaction: options.transaction }
。
如果在處理操作的過程中已經呼叫了該 hook ,則這將確保您的依賴讀/寫是同一事務的一部分。 如果 hook 沒有被處理,你只需要指定{ transaction: null }
並且可以預期預設行為。
如果這篇文章對您有幫助, 感謝 下方點贊 或 Star GitHub: sequelize-docs-Zh-CN 支援, 謝謝.