处理嵌套事务的简便方法

时间:2016-06-28 07:31:19

标签: node.js sequelize.js

假设有一个" addUser"功能,我们需要在里面插入一条记录" Account"表和"用户" table,所以这两个步骤也必须在一个事务中,所以我们将编写以下代码:

function addUser (userName, password) {
    sequelize.transaction(function () {
        return AccountModel.create(...)
        .then(UserModel.create(...))    
    })
}

然而,在另一个" addTeam"功能,我们需要在内部插入一条记录" Team"表并使用上述功能创建管理员用户。该函数还需要包含在事务中。

问题来了," addUser"函数有时需要开始一个新的事务,有时需要使用传入的事务。最明显的方法如下:

function addUser (userName, password, transaction) {
       let func = function (t) {
           return  AccountModel.create(..., t)
           .then(()=>UserModel.create(..., t)));
       if (transaction) func(t);
       else sequelize.transaction(x=>func(t));
}

function addTeam() {
     sequelize.transaction(x=> {
         TeamModel.create(..., x)
         .then(y=>addUser(x));
     });
}

显然,这太糟糕了。如何轻松处理它,让交易对调用者完全透明,如下所示:

@Transaction
async function addUser(userName, password) {
    await AccountModel.create(...);
    await UserModel.create(...);
}

@Transaction
async function addTeam(...) {
    await TeamModel.create(...);
    await addUser(...);
} 

5 个答案:

答案 0 :(得分:3)

如果你使用CLS,一个非常简单的辅助函数就能完成这项工作。



const cls = require('continuation-local-storage');
const Sequelize = require('sequelize');
const NAMESPACE = 'your-namespace';

// Use CLS for Sequelize
Sequelize.cls = cls.createNamespace(NAMESPACE);
const sequelize = new Sequelize(...);

/* * * * * * * * * * * * * * * * * * * * *
 * THE MAGIC: Create a transaction wrapper
 * * * * * * * * * * * * * * * * * * * * */

function transaction(task) {
  return cls.getNamespace(NAMESPACE).get('transaction') ? task() : sequelize.transaction(task);
};

/* * * * * * * * * * * * * * * * * * * * *
 * Your code below
 * * * * * * * * * * * * * * * * * * * * */

function addUser(userName, password) {
  return transaction(function() {
    return AccountModel
      .create(...)
      .then(() => UserModel.create(...));
  });
}

function addTeam() {
  return transaction(function() {
    return TeamModel
      .create(...)
      .then(() => addUser(...));
  });
}




答案 1 :(得分:2)

sequelize.transaction接受一个选项对象 - 如果设置了options.transaction,这将在事务中创建一个保存点(假设SQL方言支持它),否则它将创建一个新事务

http://docs.sequelizejs.com/en/latest/api/sequelize/#transactionoptions-promise

所以你应该能够做到

sequelize.transaction({ transaction }, x=>func(t));

答案 2 :(得分:1)

我解决了这个问题,感觉很棒。

我使用了sequelize提供的CLS功能,如下面的代码:

let namespace = Sequelize.cls = cls.createNamespace('myschool');
export const db = new Sequelize(config.db.url);

export const trans = option => operation => async function () {
    let t = namespace.get('transaction');
    let hasTrans = !!t;
    t = t  || await db.transaction();
    try {
        let result = await operation.apply(null, arguments);
        if (!hasTrans) await t.commit();
        return result;
    }
    catch (e) {
        if (!hasTrans) await t.rollback();
        throw e;
    }
};

上面的代码只是创建一个事务并在本地上下文中没有事务时提交它,否则就离开它。

并且每个业务功能都需要一个事务才需要使用上面的高阶函数来包装如下:

export const createSchool = trans()( async (name, accountProps) => {
    let school = await SchoolModel.create({name});
    let teacher = await createTeacher({...accountProps, schoolId: school.get('id')});
    return {school, teacher};
});

答案 3 :(得分:1)

尽管有一个可接受的答案,但我花了两天时间试图使此工作有效,因此我将发布准确的答案(不带cls),因为它在会计项目中对我有用:

    try{
      // create a transaction
      let transaction = await sequelize.transaction();
      // "parent" insertion, id is auto increment, so we will need the id of inserted data
      let record = await Parent_Record.create({ 
        data_1: req.params.data1,
        data_2: req.params.data2,
        ....
        }, {transaction});
        //the array contains data depending upon the id of inserted parent transaction 
        for ( x  in dataForChildTable) {
          result = await Child_Record.create({
          id: record.id, //same transaction so we can use record.id of parent
          .....
          other_data: dataForChildTable[x].otherData}, {transaction});
      }
      //commit the transaction
      await transaction.commit();
    } catch (err) {
      console.log(err.message);
    }

答案 4 :(得分:0)

您可以使用 zb-sequelize 添加事务装饰器。

import { Transactional, Tx } from 'zb-sequelize';

@Transactional
function addUser(user, password, @Tx transaction) {
  // no need to create, commit or rollback a transaction.
}

您确实需要使用 sequelize 实例对其进行初始化。

import { Sequelize } from 'sequelize';
import { initSequelizeResolver } from 'zb-sequelize';

// you already have this somewhere.
const sequelize = new Sequelize(options);

// you need to add this:
initSequelizeResolver((args) => sequelize);