1. 程式人生 > >Sequelize 中文API文件-6. 事務的使用與Transaction類

Sequelize 中文API文件-6. 事務的使用與Transaction類

TransactionSequelize中用於實現事務功能的子類,通過呼叫Sequelize.transaction()方法可以建立一個該類的例項。在Sequelize中,支援自動提交/回滾,也可以支援使用者手動提交/回滾。

1. 事務的使用

Sequelize有兩種使用事務的方式:

  • 基於Promise結果鏈的自動提交/回滾
  • 另一種是不自動提交和回滾,而由使用者控制事務

1.1 受管理的事務(auto-callback)

受管理的事務會自動提交或回滾,你可以向sequelize.transaction方法傳遞一個回撥函式來啟動一個事務。

需要注意,在這種方式下傳遞給回撥函式的transaction會返回一個promise

鏈,在promise鏈中(thencatch)中並不能呼叫t.commit()t.rollback()來控制事務。在這種方式下,如果使用事務的所有promise鏈都執行成功,則自動提交;如果其中之一執行失敗,則自動回滾。

return sequelize.transaction(function (t) {

  // 要確保所有的查詢鏈都有return返回
  return User.create({
    firstName: 'Abraham',
    lastName: 'Lincoln'
  }, {transaction: t}).then(function (user) {
    return user.setShooter({
      firstName: 'John',
      lastName: 'Boothe'
    }, {transaction: t});
  });

}).then(function (result) {
  // Transaction 會自動提交
  // result 是事務回撥中使用promise鏈中執行結果
}).catch(function (err) {
  // Transaction 會自動回滾
  // err 是事務回撥中使用promise鏈中的異常結果
});

丟擲錯誤並回滾

使用受管理的事務時,不能通過手工呼叫的方式來提交或回滾事務。但在需要時(如驗證失敗),可以通過throw來丟擲異常回滾事務。

return sequelize.transaction(function (t) {
  return User.create({
    firstName: 'Abraham',
    lastName: 'Lincoln'
  }, {transaction: t}).then(function (user) {
    // 注意,雖然所有操作成功但仍會回滾
    throw new Error();
  });
});

自動傳遞事務到所有的查詢中

在上面的示例中,我們通過向第二個引數中新增{ transaction: t }

選項來手工傳遞事務。如果要自動傳遞事務到所有的查詢中,需要安裝continuation local storage(CLS)模組並在程式碼中建立一個名稱空間例項:

var cls = require('continuation-local-storage'),
    namespace = cls.createNamespace('my-very-own-namespace');

啟用CLS,需要在Sequlize建構函式屬性中設定名稱空間:

var Sequelize = require('sequelize');
Sequelize.cls = namespace;

new Sequelize(....);

注意cls必須在constructor建構函式中設定,而不能在sequlize例項中設定。

CLS的工作方式就像一個回撥函式的本地執行緒儲存。在sequlize中啟用CLS後,需要在啟動事務時設定transaction名稱空間。在一個回撥鏈中設定的變數時私有的,所以幾個併發事務可以同時存在。

sequelize.transaction(function (t1) {
  namespace.get('transaction') === t1; // true
});

sequelize.transaction(function (t2) {
  namespace.get('transaction') === t2; // true
});

大多數情況下,你不用通過namespace.get('transaction')直接訪問名稱空間,因為所有的查詢都會自動查詢事務的名稱空間。

sequelize.transaction(function (t1) {
  // 啟用 CLS 後,會在自動在事務中執行create 操作
  return User.create({ name: 'Alice' });
});

1.2 不受管理的事務(then-callback)

不受管理的事務需要你強制提交或回滾,如果不進行這些操作,事務會一直保持掛起狀態直到超時。

啟動一個不受管理的事務,同樣是呼叫sequelize.transaction()方法,但不傳遞迴調函式引數(仍然可以傳遞選項引數)。然後可以在其返回的promisethen方法中手工控制事務:

return sequelize.transaction().then(function (t) {
  return User.create({
    firstName: 'Homer',
    lastName: 'Simpson'
  }, {transaction: t}).then(function (user) {
    return user.addSibling({
      firstName: 'Lisa',
      lastName: 'Simpson'
    }, {transaction: t});
  }).then(function () {
    return t.commit();
  }).catch(function (err) {
    return t.rollback();
  });
});

1.3 並行/部分事務

在一系列的查詢中可以有多個並行的事務或者其中的一些查詢不使用事務。通過{transaction: }選項來指定查詢屬於哪個事務:

sequelize.transaction(function (t1) {
  return sequelize.transaction(function (t2) {
    // 啟用 CLS 時, 查詢會自動使用 t2
    // 通過 `transaction` 選項可以改變查詢所屬的事務
    return Promise.all([
        User.create({ name: 'Bob' }, { transaction: null }),
        User.create({ name: 'Mallory' }, { transaction: t1 }),
        User.create({ name: 'John' }) // 預設使用 t2
    ]);
  });
});

1.4 隔離級別

在啟動事務時,可以設定事務的隔離級別。可選值有:

Sequelize.Transaction.ISOLATION_LEVELS.READ_UNCOMMITTED // "READ UNCOMMITTED"
Sequelize.Transaction.ISOLATION_LEVELS.READ_COMMITTED // "READ COMMITTED"
Sequelize.Transaction.ISOLATION_LEVELS.REPEATABLE_READ  // "REPEATABLE READ"
Sequelize.Transaction.ISOLATION_LEVELS.SERIALIZABLE // "SERIALIZABLE"

預設的隔離級別為REPEATABLE READ,如果需要修改可以在啟動事務時在第一個引數中設定:

return sequelize.transaction({
  isolationLevel: Sequelize.Transaction.ISOLATION_LEVELS.SERIALIZABLE
  }, function (t) {

  // your transactions

});

1.5 選項引數

呼叫transaction方法時,可以向其第一個引數中傳遞一個選項引數,通過該引數可以對事務進行一些配置:

return sequelize.transaction({ /* options */ });

預設配置選項如下:

{
  autocommit: true,
  isolationLevel: 'REPEATABLE_READ',
  deferrable: 'NOT DEFERRABLE' // implicit default of postgres
}

isolationLevel選項可以在初始化Sequelize全域性設定,也可以啟動每個事務時區域性設定:

// 全域性設定
new Sequelize('db', 'user', 'pw', {
  isolationLevel: Sequelize.Transaction.ISOLATION_LEVELS.SERIALIZABLE
});

// 區域性設定
sequelize.transaction({
  isolationLevel: Sequelize.Transaction.ISOLATION_LEVELS.SERIALIZABLE
});

deferrable選項會在事務執行前或執行後啟動一個額外的查詢,但需要注意此選項僅適用於PostgreSQL

sequelize.transaction({
  // to defer all constraints:
  deferrable: Sequelize.Deferrable.SET_DEFERRED,

  // to defer a specific constraint:
  deferrable: Sequelize.Deferrable.SET_DEFERRED(['some_constraint']),

  // to not defer constraints:
  deferrable: Sequelize.Deferrable.SET_IMMEDIATE
})

2. Transaction

2.1 訪問與初始化

Transaction物件用於標識一個要執行的事務,可以通過以下方式建立該物件的例項:

sequelize.transaction(options, autoCallback);    // sequelize 是一個Sequelize物件例項

在建立事務例項,並在事務中執行查詢時,需要傳入一個可選引數物件。各引數值如下:

名稱型別說明
sequelizeSequelize一個已配置的sequelize例項
optionsObject選項物件
options.autocommit=trueBoolean設定事務的autocommit(自動完成)屬性
options.type=trueString設定事務型別,詳見TYPES
options.isolationLevel=trueString
options.deferrableString設定立即或延遲檢查約束

TYPES

此選項僅Sqlite適用,用於設定事務型別,預設為DEFERRED,也可以在new Sequelize()時由options.transactionType選項指定。可選值如下:

{
  DEFERRED: "DEFERRED",
  IMMEDIATE: "IMMEDIATE",
  EXCLUSIVE: "EXCLUSIVE"
}

我可以像下面這樣設定事務型別:

return sequelize.transaction({
  type: Sequelize.Transaction.EXCLUSIVE
}, function (t) {

 // 事務

}).then(function(result) {
  // 事務已完成(commit)
}).catch(function(err) {
  // 出現異常
});

ISOLATION_LEVELS

通過options.isolationLevel選項設定sequelize.transaction啟動事務時的隔離級別。預設為REPEATABLE_READ,也可以在new Sequelize()時由options.isolationLevel選項指定。可選值如下:

{
  READ_UNCOMMITTED: "READ UNCOMMITTED",
  READ_COMMITTED: "READ COMMITTED",
  REPEATABLE_READ: "REPEATABLE READ",
  SERIALIZABLE: "SERIALIZABLE"
}

如,可以像下面這樣設定事務的隔離級別:

return sequelize.transaction({
  isolationLevel: Sequelize.Transaction.SERIALIZABLE
}, function (t) {

 // 事務

}).then(function(result) {
  // 事務已完成(commit)
}).catch(function(err) {
  // 出現異常
});

LOCK

在呼叫find等方法時,可以新增一個lock選擇用於鎖定正在使用的資料行:

t1 // 一個事務
t1.LOCK.UPDATE,
t1.LOCK.SHARE,
t1.LOCK.KEY_SHARE, // Postgres 9.3+ only
t1.LOCK.NO_KEY_UPDATE // Postgres 9.3+ only

使用

t1 // 一個事務
Model.findAll({
  where: ...,
  transaction: t1,
  lock: t1.LOCK...
});

Postgres還支援通過of選項實現預鎖定:

UserModel.findAll({
  where: ...,
  include: [TaskModel, ...],
  transaction: t1,
  lock: {
    level: t1.LOCK...,
    of: UserModel
  }
});

2.2 例項方法

通過sequelize.transaction()啟動事務後,在例項中包含兩個方法commit()rollback()分別用於完成事務和事務回滾。

commit()完成事務

該方法用於手動完成事務。

如,我們可以像下面這樣完成一個事務:

sequelize.transaction(function (t) {
  // 事務
  Model.findAll().then(function(result){
    // 手動提交/完成事務
    t.commit();
  });
}).then(function(result) {
  // 事務已完成(commit)
}).catch(function(err) {
  // 出現異常
});

rollback()回滾事務

該方法用於手動回滾事務。

sequelize.transaction(function (t) {
  // 事務
  Model.findAll().then(function(result){
    // 手動回滾事務
    t.rollback();
  });
}).then(function(result) {
  // 事務已完成(commit)
}).catch(function(err) {
  // 出現異常
});