Sequelize 中文文件 v4 – Hooks – 鉤子

DemoPark發表於2019-02-16

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 對於相關聯的例項而言將是一樣的,除了幾件事情之外。

  1. 當使用 add/set 函式時,將執行 beforeUpdate/afterUpdate hook。
  2. 呼叫 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 支援, 謝謝.

相關文章