MongoDB查询结果集在执行查询后被修改

4

我的应用程序中有2个线程:

  1. 爬网站并将数据插入MongoDB

  2. 检索已经爬取的网站并执行业务逻辑

为了检索已经爬取的网站,我使用以下查询语句:

Document query = new Document("fetchStatus", new Document("$lte", fetchStatusParam));
FindIterable<Document> unfetchedEpisodes = dbC_Episodes.find(query);

我得到的结果是所有集数,其fetchStatusParam小于或等于特定值。

接下来,我将结果集中的项目存储在HashMap<String, TrackedEpisode>中,这是一个对象属性,以便跟踪它们:

for (Document document : unfetchedEpisodes) {
    this.trackedEpisodes.put(document.get("_id").toString(), new TrackedEpisode(document));
}

然后我执行一些业务逻辑,其中:

  • 不会 修改 unfetchedEpisodes 结果集。

  • 不会trackedEpisodes 中删除任何对象。

到目前为止,一切都正常。
最后一步,我遍历所有检索到的文档,并将它们标记为已获取,以防止未来重复获取。

for (Document document : unfetchedEpisodes) {

    if (this.trackedEpisodes.containsKey(document.get("_id").toString())) {

        // prevent repeated fetching
        document.put("fetchStatus", FetchStatus.IN_PROCESS.getID());

        if (this.trackedEpisodes.get(document.get("_id").toString()).isExpired()) {
            document.put("isExpired", true);
            document.put("fetchStatus", FetchStatus.FETCHED.getID());
        }
    } else {
        System.out.println("BOO! Strange new object detected");
    }

    dbC_Episodes.updateOne(new Document("_id", document.get("_id")), new Document("$set", document));
}

我运行这段代码几天,并注意到有时会进入if (this.trackedEpisodes.containsKey())语句的else部分。对我来说很奇怪,unfetchedEpisodestrackedEpisodes为什么不同步并且不包含相同的项呢?
我开始调查此问题并注意到在我到达"BOO!检测到奇怪的新对象"的时间,document迭代器包含了数据库中的项目,但不应该出现在unfetchedEpisodes中,因为我没有执行新的数据库查询。
我多次检查了将检索到的项目存储到trackedEpisodes的问题,并且总是将所有元素从unfetchedEpisodes添加到trackedEpisodes中,但是之后有时仍然会进入"BOO!检测到奇怪的新对象"
我的问题:
  1. 为什么在执行查询后,unfetchedEpisodes会得到新的项?

  2. 在执行Collection#query()后,MongoDB驱动程序是否可能修改unfetchedEpisodes

  3. 也许我应该在执行MongoDB查询后使用某种 .close()

使用的版本:
  • MongoDB:3.2.3,x64

  • MongoDB Java Driver:mongodb-driver-3.2.2mongodb-driver-core-3.2.2bson-3.2.2


请添加一个最小化、完整和可验证的示例 - Markus W Mahlberg
1
从我所看到的,我认为你的应用程序架构还有一些可以改进的地方。这是我的建议:使用生产者(爬虫)将新站点发送到管道中,其中一个步骤是持久化数据,并根据需要在之前和之后应用业务逻辑。Camel非常适合此类任务。 - Markus W Mahlberg
@MarkusWMahlberg,很难提供最小、完整和可验证的示例,而不提供整个爬虫项目。 - Mike
好的,你必须付出一些努力;) 但是请仔细看看我的建议。逐个处理每个站点,可能使用多个线程,将使您的生活变得更加轻松。 - Markus W Mahlberg
1
http://camel.apache.org/rss.html - Markus W Mahlberg
显示剩余2条评论
1个回答

2
当你在这里调用find函数时:
FindIterable<Document> unfetchedEpisodes = dbC_Episodes.find(query);

您实际上并没有得到所有的剧集,而是得到了一个指向匹配文档的数据库游标。

然后当您调用:

for (Document document : unfetchedEpisodes){}

创建一个迭代器,遍历所有与查询匹配的文档。

第二次调用时,会返回一个新的游标,针对相同的查询,会遍历所有现在匹配的文档。

如果集合在两次调用之间发生了更改,则结果将不同。

如果您希望确保unfetchedEpisodes的内容未更改,则可以选择将整个结果集加载到内存中,并在内存中进行迭代,而不是在数据库上进行迭代,例如:

ArrayList<Document> unfetchedEpisodes = dbC_Episodes.find(query).into(new ArrayList<Document>());

谢谢您的解释,现在我明白为什么它会改变了。顺便问一下,假设数据库游标的工作方式类似于指针,如果我在 for (Document document : unfetchedEpisodes){} 中更改了 document,那么这个更改将反映在数据库中而无需进行任何其他操作(例如 updateOne())是否正确? - Mike
1
当你在循环内访问document时,它已经被检索到内存中,只有应用程序代码中的对象会被更新,没有同步回数据库。你需要执行一个更新操作。 - sheilak

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接