mongodb:如果未更新,则仅更新文档

时间:2017-05-25 15:43:43

标签: mongodb validation mongoose

我有这个功能。仅允许服务: 仅在可用参数为真时才采用。

function takeService(req, res) {
  var serviceId = req.params.id;
  var driverId = req.body.driverId;

  Service.findById(serviceId, (err, service) =>{
    if (!err) {
      if (!service) {
        res.status(404).send({message: 'Not found'});
      } else {
        if (service.available === false ) {
          res.status(409).send({message: 'The service is taken'});
        } else {
          Service.findByIdAndUpdate(serviceId, {
            driverId, 
            status: 1, 
            available: false
          }, (err, serviceUpdated) =>{
            if (!err && serviceUpdated) {
              res.status(200).send({message: "tomado"});
            }
          });
        }
      }
    }
  });
}

架构:

var ServiceSchema = Schema({
  clientId: {
    type: String,
    ref: 'Client'
  },
  available: Boolean,
  routeId: {
    type: String,
    ref: 'Route'
  },
  date: Date,
  radius: Number,
  driverId: {
    type: String,
    ref: 'Driver'
  },
  status: Number,
  time: String,
  createdTime: Number,
  rateId: {
    type: String,
    ref: 'Rate'
  }
});

var DriverSchema = Schema({
  name: String,
  surname: String,
  username: String,
  password: String,
  status: { type: Number, default: 0 },
  oneSignalId: String,
  plate: String,
  make: String,
  year: String,
  model: String,
  groupId: [{
    type: String,
    ref: 'DriverGroup'
  }],
  unit: String,
  telephone: String
});

问题是当两个设备调用此函数时,在某些情况下,它们都找到文档并检查是否可用,然后两者都更新同一文档。我正在寻找模式中的一些验证来自动检查这个属性。

1 个答案:

答案 0 :(得分:0)

如果我正确理解问题,主要问题是两台设备可能认为服务仍然可用。

这样做的最终原因是findByIdfindByIdAndUpdate之间存在竞争条件:在这两个电话之间,有一个时间窗口,其中另一个请求可以改变数据库中的文档。

要解决此问题,您可以使用原子findAndModify命令,Mongoose将此命令公开为Model#findOneAndUpdate

您的代码会变成这样:

function takeService(req, res) {
  var serviceId = req.params.id;
  var driverId  = req.body.driverId;

  Service.findOneAndUpdate({
    _id       : serviceId,
    available : true
  }, {
    driverId  : driverId,
    status    : 1,
    available : false,
  }, (err, service) => {
    if (err) {
      return res.status(500);
    } else if (! service) {
      return res.status(409).send({message: 'The service is taken'});
    } else {
      return res.status(200).send({message: "tomado"});
    }
  });
}

您应该注意与原始代码存在一些差异:

  • 您无法区分不存在的服务(无效/未知serviceId)和不再可用的服务;在这两种情况下,更新都不会产生任何结果,并且会发回409响应;
  • findOneAndUpdate将在更新之前返回文档。如果要接收更新的文档,请在查询中传递new选项:

    Service.findOneAndUpdate({ ... }, { ... }, { new : true }, (err, service) => { ... })
    
  • 我在那里添加了一个错误处理程序,它会发回500("内部服务器错误" )响应。