MongoCollection.findOneAndUpdate() 中的 $inc 与 $setOnInsert

Xtr*_*der 5 mongodb

我正在尝试在 mongodb 中实现一个'server-side counter-versioned'项目并尝试执行以下操作 /*使用 Java API */

Document dbDoc = dbCollection.findOneAndUpdate(
        new Document("_id", "meta"), 
        new Document("$inc", new Document("version", 1))
             .append("$setOnInsert", new Document("version", 0)),
        new FindOneAndUpdateOptions().upsert(true)
                                     .returnDocument(ReturnDocument.AFTER));
Run Code Online (Sandbox Code Playgroud)

假设的逻辑很简单:如果数据库中没有记录 - 从零开始计数(并且使用全新的新对象),否则 - 递增计数器。

代码示例失败并显示:'Cannot update 'version' and 'version' at the same time'

我的假设是,在“upsert”模式下,当没有找到匹配的项目时,mongo 应该只使用“$setOnInsert” - 但它以其他方式工作。

是否可以在一个原子mongoDB 调用中实现这样的操作?


PS:关于 findOneAndUpdate() 和 upsert() 的MongoDB 文档很模糊 - 至少我无法从他们的描述中理解为什么会出现这个错误。

这里也有类似的问题 - findAndModify 失败并出现错误:“无法同时更新 'field1' 和 'field1' - 已接受,但同样没有明确的推理。

Kev*_*ith 4

您可以删除更新运算符,因为如果文档不存在,$setOnInsert这将被设置为运算符中指定的值$inc

https://docs.mongodb.com/v3.2/reference/operator/update/inc/#behavior

如果该字段不存在,$inc 将创建该字段并将该字段设置为指定值。

来自 mongo shell 的示例:

> db.dropDatabase()
{ "ok" : 1 }
> db.test.findOneAndUpdate({_id: "meta"}, { $inc: { version: 1} }, {upsert: true, returnNewDocument: true})
{ "_id" : "meta", "version" : 1 }
> db.test.findOneAndUpdate({_id: "meta"}, { $inc: { version: 1} }, {upsert: true, returnNewDocument: true})
{ "_id" : "meta", "version" : 2 }
> db.test.findOneAndUpdate({_id: "meta"}, { $inc: { version: 1} }, {upsert: true, returnNewDocument: true})
{ "_id" : "meta", "version" : 3 }
> db.test.findOneAndUpdate({_id: "meta"}, { $inc: { version: 1} }, {upsert: true, returnNewDocument: true})
{ "_id" : "meta", "version" : 4 }
> db.test.findOneAndUpdate({_id: "meta"}, { $inc: { version: 1} }, {upsert: true, returnNewDocument: true})
{ "_id" : "meta", "version" : 5 }
Run Code Online (Sandbox Code Playgroud)

如果您需要在第一次初始插入时设置给定版本,那么 mongodb 不支持任何操作符来自动支持此操作,但是以下操作是安全的,并且是常见的解决方法:

> db.dropDatabase()
{ "dropped" : "test", "ok" : 1 }
> function updateMeta(){
...   function update(){
...     return db.test.findOneAndUpdate({_id: "meta"}, { $inc: { version: 1} }, {returnNewDocument: true});
...   }
...
...   var result = update();
...
...   if(result === null){
...     db.test.insert({_id: "meta", version: -10});
...     result = update();
...   }
...
...   return result;
... }
>
> updateMeta()
{ "_id" : "meta", "version" : -9 }
> updateMeta()
{ "_id" : "meta", "version" : -8 }
> updateMeta()
{ "_id" : "meta", "version" : -7 }
> updateMeta()
{ "_id" : "meta", "version" : -6 }
> updateMeta()
{ "_id" : "meta", "version" : -5 }
>
Run Code Online (Sandbox Code Playgroud)