当从数据库本身获取modelObject时,modelObject.save()是否只更新现有的数据库文档



要使用一个演示问题的示例,假设我有一个由以下模式定义的用户模型:

var UserSchema = new Schema({ 
username: String,
email: String
}
mongoose.model('User', UserSchema);

我知道,要使用保存方法更新用户,我可以查询用户,然后保存更改,如下所示:

User.findOne({username: usernameTofind}, function(err, user) {
//ignore errors for brevity
user.email = newEmail;
user.save(function(err) { console.log('User email updated') });
});

但是,如果我尝试创建一个具有完全相同字段值(包括_id)的新User对象,是否有可能覆盖数据库文档?我认为不会,因为理论上这意味着恶意用户可以利用不安全的api并覆盖现有文档(例如,使用"创建新帐户"请求,这不会/不能依赖于已经通过身份验证的用户),但更重要的是,当我尝试使用请求工具(我使用Postman,但我相信类似的curl命令就足够了)来完成这项工作时,我会得到一个重复的_id错误

MongoError: insertDocument :: caused by :: 11000 E11000 duplicate key error index

因此,我只想澄清一下,更新现有文档的唯一方法是查询文档,修改返回的实例,然后在该实例上调用save方法,或者使用静态update()。这两者都可以通过要求身份验证来保护。

如果有帮助的话,我提出这个问题的动机如上所述,因为我想确保用户在以下方法公开的情况下无法覆盖现有文档:

userCtrl.create = function(req, res, next) {
var user = new User(req.body);
user.save(function(err) {
if (err) {
return next(err);
} else {
res.json(user);
}
});
};

快速编辑:我刚刚意识到,如果是这样的话,那么数据库如何知道查询的实例和具有完全相同键和属性的新用户对象之间的区别?

modelObject.save()是否仅在以下情况下更新现有数据库文档modelObject是从数据库本身获得的?

是的。有一个标志指示文档是否为新文档。如果是新文档,Mongoose将插入该文档。如果没有,则它将更新文档。标志为Document#isNew

当您find文档时:

User.findOne({username: usernameTofind}, function(err, user) {
//ignore errors for brevity
console.log(user.isNew) // => will return false
});

创建新实例时:

var user = new User(req.body);
console.log(user.isNew) // => will return true

所以我只想澄清一下,更新现有document是查询文档,修改返回的实例,然后在该实例上调用save方法,或者使用staticupdate()。这两者都可以通过要求身份验证来保护。

还可以使用Model#updateModel.findOneAndUpdate和其他方法更新文档。

但是,不能更新_id字段。即使Mongoose还没有发出正确的数据库命令,MongoDB也不允许这样做。如果你尝试它,你会得到这样的错误:

The _id field cannot be changed from {_id: ObjectId('550d93cbaf1e9abd03bf0ad2')} to {_id: ObjectId('550d93cbaf1e9abd03bf0ad3')}.

但是,假设您使用问题中的最后一段代码来创建新用户,Mongoose将发出insert命令,因此无法覆盖现有文档。即使MongoDB在请求体中传递了_id字段,它也会抛出E11000 duplicate key error index错误。

此外,在使用字段创建用户之前,您应该筛选用户可以作为有效负载传递的字段。例如,您可以创建一个接收对象和允许参数数组的通用函数:

module.exports = function(object, allowedParams) {
return Object.keys(object).reduce(function(newObject, param) {
if (allowedParams.indexOf(param) !== -1)
newObject[param] = object[param];
return newObject;
}, {});
}

然后你只需要并使用过滤请求主体的功能:

var allow = require('./parameter-filter');
function filter(params) {
return allow(params, ["username", "email"]);
}
var user = new User(filter(req.body));

最新更新