如何在事务中插入后保存一对多关系

时间:2017-05-07 14:45:34

标签: javascript knex.js bookshelf.js

我在Node v6.9.5上的Express v4.15.2服务器中使用Bookshelf v0.10.3和Knex v0.12.9。

我有一个user表,其中包含一个关联的identity表,由identity_user表连接。这些表是使用Knex创建的:

Promise.all([
  knex.schema.createTable('user', table => {
    table.increments('id');
    table.string('username');
    table.string('password');
    table.unique('username');
  }),
  knex.schema.createTable('identity', table => {
    table.increments('id');
    table.string('type');
    table.string('value');
  }),
  knex.schema.createTable('identity_user', table => {
    table.integer('user_id').references('user.id');
    table.integer('identity_id').references('identity.id');
    table.unique(['user_id', 'identity_id']);
  })
]);

我的Bookshelf模型如下所示:

bookshelf.plugin([ 'registry', 'bookshelf-camelcase' ]);

const Identity = bookshelf.model('Identity', {
  tableName: 'identity',

  user() {
    return this.belongsTo('User', 'identity_user');
  }
});

const User = bookshelf.model('User', {
  tableName: 'user',

  initialize() {
    this.on('saving', model => {
      if (model.hasChanged('username')) {
        return model.set('username', String(model.attributes.username).toLowerCase());
      }
    });
    this.on('saving', model => {
      if (model.hasChanged('password')) {
        return bcrypt.hash(model.attributes.password, 10)
          .then(hash => model.set('password', hash));
      }
    });
  },

  identities() {
    return this.hasMany('Identity', 'identity_user');
  }
});

在我的createUser函数中,我正在尝试在单个事务中插入新用户和身份。

function createUser(req, res, next) {
  const { email, username, password } = req.body;

  // some data validation

  return bookshelf.transaction((t) => {
    return new User({ username, password } ).save(null, { transacting: t })
      .then(user => new Identity({ type: 'email', value: email }).save({ userId: user.attributes.id }, { transacting: t }))
      .then(() => res.sendStatus(204))
      .catch(err => {
        // handle PostgreSQL unique violation error
      });
  });
}

当我运行服务器并尝试注册新用户时,我收到以下错误:

insert into "identity" ("type", "user_id", "value") values ($1, $2, $3) returning "id" - column "user_id" of relation "identity" does not exist { error: insert into "identity" ("type", "user_id", "value") values ($1, $2, $3) returning "id" - column "user_id" of relation "identity" does not exist

这是PostgreSQL错误(代码42703 - 未定义列),但似乎所有内容都设置正确。我真的可以用另一双眼睛看这个。我错过了什么?

提前致谢!

1 个答案:

答案 0 :(得分:1)

错误是因为Bookshelf发现'userId'被插入 Identity 并将列名称推断为'user_id'。但是,要连接到MxN关系,您需要使用attach()代替:

function createUser(req, res, next) {
  const { email, username, password } = req.body;

  // some data validation

  return bookshelf.transaction((t) => {
    return new User({ username, password } )
      .save(null, { transacting: t })
      .then(user => new Identity({ type: 'email', value: email })
        .save(null, { transacting: t }))
      .then(identity => user.identities()
        .attach(identity.id, { transacting: t }));
  })
  .then(() => res.sendStatus(204))
  .catch(err => {
    // handle PostgreSQL unique violation error
  });
}

似乎对我有用的变体是直接附加模型(例如.attach(identity, { transacting: t })),但文档不支持此方法。

修改我忽略了您的模型定义上的两个错误,hasMany()仅在使用through()调用时映射到MxN关系,这需要连接表为映射为模型。在上面的例子中,我认为使用简单的'belongsToMany()`调用就足够了。因此,请通过以下方式替换您的关系定义:

user() {
  return this.belongsToMany('User');
}

identities() {
  return this.belongsToMany('Identity');
}

Edit2 :将catch()移至事务范围之外,因此异常将自动触发回滚。

Edit3 :从事务范围中删除更详细的then()链和非db内容。