【问题标题】:What is the correct method to update a document while using Mongo Cursor?使用 Mongo Cursor 时更新文档的正确方法是什么?
【发布时间】:2017-01-20 16:39:44
【问题描述】:

我正在尝试使用游标更新 Mongo DB 中的文档。我的 Mongo DB Java 驱动程序版本是 3.3.0。以下是我的代码 sn-p。

MongoCollection<Document> collection = mongoDb.getCollection("customer");
MongoCursor<Document> cursor = collection.find().iterator();
try{
    while(cursor.hasNext()){
        Document oldDoc = cursor.next();
        //created new Document newDoc
        collection.replaceOne(oldDoc, newDoc);
    }
}catch(Exception e){
    e.printStackTrace();
}

虽然这样,我可以更新文档,我认为这不是有效的方法,因为这里的集合被搜索了 2 次。我想用一些值更新旧文档,然后再更新想要使用 collection.update(oldDoc)collection.save(oldDoc) 等方法保存它而不创建新文档。我搜索并发现了以下帖子。

Java, MongoDB: How to update every object while iterating a huge collection?

这正是我想要的,但我在新 API 中找不到 save() 方法。所以我在这里有 3 个问题。

  1. Mongo DB Java 驱动程序 3.3.0 API 中的 save() 方法是什么,我可以通过它在迭代游标时更新或保存文档?李>
  2. 是否有任何方法可以更新现有文档而无需在 Mongo DB Java 驱动程序 3.3.0 API 中创建新文档?
  3. 以上链接显示 save() 之前是 Mongo DB Java 驱动程序 API 的一部分。任何专家的答案,为什么它从 API 中被删除?

【问题讨论】:

  • 你能看看this吗?

标签: java mongodb mongodb-java mongodb-java-3.3.0


【解决方案1】:

不用担心额外的搜索,replaceOne 方法的第一个参数是一个过滤器。使用旧文档作为过滤器将使用文档的 _id 属性,该属性应该相对较快。 所以基本上,你这样做的方式是正确的。但请记住,该操作是非事务性的,这意味着更新可能会影响游标的结果集。因此,可能会发生更新的文档将存储在光标尚未通过的位置,从而导致文档被处理两次。

关于您的问题。

  1. mongo shell 中save 方法的语义是更新或插入尚不存在的文档(又名 upsert)。与MongoCollection 最接近的是updateOne(),其中UpdateOptions upsert 设置为true。这基本上就是save 命令的作用。但是如果替换文档是原始文档的修改版本,replaceOne 会导致相同的结果。

  2. 为什么要担心对象创建?在 java 中,它几乎没有成本,而且不用担心 GC 或内存消耗,因为那些短暂的对象被 GC 处理得非常快。如果您想让代码更简洁,您可以只使用 oldDoc 的 _id 创建一个专用过滤器对象,修改 oldDoc 并使用它而不是新 Doc。

例如:

UpdateOptions upsert = new UpdateOptions().upsert(true);
Bson idFilter = Filters.eq("_id", oldDoc.getObjectId("_id"));
//modify the oldDoc
oldDoc. ...
collection.replaceOne(idFilter, oldDoc, upsert);

【讨论】:

  • findOneAndUpdate() 呢?
  • 当只有一个文档与过滤器匹配时,两者的行为方式相同。但是当多个文档匹配时表现不同。它还返回未修改的文档,但如果更新的文档依赖于该数据, findAndUpdate 将无济于事。从技术上讲,findAndUpdatefindAndModify 是一个命令,而 save() 是一个 insert(),如果更新的文档有一个 _id 或一个 update(),如果它没有 _id,则带有 upsert=true。
  • 我还是不明白replaceOne 怎么更接近save()。您不能使用replaceOne 修改(更新)。您可以使用update*,它更接近savefind*Update*,就像我之前提到的那样。
  • 其中许多方法具有相似和重叠的行为以及细微的差异,具体取决于您传递要替换的文档,结果行为可能与更新相同。为了理解这种行为,查看实现有时会有所帮助,即在 shell db.yourCollection.save(不带括号)中显示了 javascript 实现。 save 是用insertupdate 实现的,findAndModify 是一个命令,replaceOne 是一个批量操作。但是java驱动中没有save,最接近的函数会用upsert=true更新。