Sequelize 中文文檔 v4 - Hooks - 鉤子

Hooks - 鉤子

此係列文章的應用示例已發佈於 GitHub: sequelize-docs-Zh-CN. 能夠 Fork 幫助改進或 Star 關注更新. 歡迎 Star.git

Hook(也稱爲生命週期事件)是執行 sequelize 調用以前和以後調用的函數。 例如,若是要在保存模型以前始終設置值,能夠添加一個 beforeUpdate hook。github

獲取完整列表, 請查看 Hooks file.sql

操做清單

(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() 將會刪除它們。promise

全局 / 通用 Hook

全局 hook 是全部模型的 hook。 他們能夠定義您想要的全部模型的行爲,而且對插件特別有用。 它們能夠用兩種方式來定義,它們的語義略有不一樣:app

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 支持, 謝謝.

相關文章
相關標籤/搜索