当父文档可能不存在时更新MongoDB子文档

这里是我的数据,包括书籍collections,还有books.reviews子集合。

books = [{ _id: ObjectId("5558f40ad498c653748cf045"), title: "Widget XYZ", isbn: 1234567890, reviews: [ { _id: ObjectId("5558f40ad498c653748cf046"), userId: "01234", rating: 5, review: "Yay, this great!" }, { _id: ObjectId("5558f40ad498c653748cf047"), userId: "56789", rating: 3, review: "Meh, this okay." } ] }] 

在Node.js(使用Mongoose)中,我需要用户能够通过表单提交评论,并将评论和书籍发送到服务器,并具有以下条件:

  1. 如果图书不存在已经具有特定的isbn,添加它,然后添加审查(它显然不存在)。
  2. 如果这本书确实存在…
    • 如果此用户的评论不存在,请添加。
    • 如果该用户的评论确实存在,请对其进行更新。

我可以用4个单独的查询来做到这一点,但我知道这不是最佳的。 什么是我可以使用的最less数量的查询(当然,这些查询是什么)?

你基本上有三种情况:

  1. 这本书和评论都存在。 这是一个简单的$set
  2. 该书存在,但不是审查。 这需要$push
  3. 该书不存在。 这需要{upsert:1}$setOnInsert

我无法find一种方法来统一其中任何两个,而不会损害数据完整性,以防发生故障(请记住,MongoDB没有primefaces事务)。

所以最好的想法是以下几点:

 // Case 1: db.books.update({isbn:'1234567890', review: { $elemMatch: {userID: '01234'}}}, {$set: {'review.$.rating': NEW_RATING}} ) // Case 2: db.books.update({isbn:'1234567890', review: { $not: { $elemMatch: {userID: '01234'}}}}, {$push: {review: {rating: NEW_RATING, userID:'01234'}}} ) // Case 3: db.books.update({isbn:'1234567890'}, {$setOnInsert: {review: [{rating: NEW_RATING, userID:'01234'}]}}, {upsert:1} ) 

你可以盲目地运行这三个更新,因为它们之间没有重叠的情况。 事情的美妙之处在于所有这些操作都是幂等的 。 所以你可以申请一次或几次,总是得到相同的结果。 这在故障转移的情况下尤为重要。 另外,如果您的数据库出现故障,您的数据库不可能不一致,也不可能丢失现有的数据。 在最坏的情况下,审查不会更新。 最后,即使在并发更新的情况下,这也应该保证数据的一致性(即:在这种情况下,一个更新将会覆盖另一个更新,但是不应该最终为同一本书提供两个文档,或者同一个用户的两个评论相同书)。
由于在这里迟了,所以后面的点必须得到证实,所以我的分析可能会有些疑问。

最后,如果你想减lessMongoDB和你的应用之间的往返次数,你可以看一下update database命令,允许你在一个命令中包装几个更新。