Overview
在本指南中,您可以学习;了解如何使用Scala驾驶员执行复合操作。
复合操作由作为原子操作执行的读取操作和写入操作组成。原子操作是指要么完全完成,要么根本未完成的操作。
原子操作可以帮助您避免代码中出现争用情况。当代码的行为取决于不可控事件的顺序时,就会出现争用情况。
MongoDB 支持以下复合操作:
查找并更新一个文档
查找并替换一个文档
查找并删除一个文档
如何使用复合运算
本节介绍如何使用Scala驾驶员执行复合操作。
以下示例使用包含两个示例文档的集合:
{"_id": {"$oid": "1"}, "food": "donut", "color": "green"} {"_id": {"$oid": "2"}, "food": "pear", "color": "yellow"}
这些示例使用以下 collection 引用将每个文档映射到 Document 类的实例:
val database: MongoDatabase = mongoClient.getDatabase("compound_operations") val collection: MongoCollection[Document] = database.getCollection("example") val hotelCollection: MongoCollection[Document] = database.getCollection("rooms")
每个复合操作都返回一个发出匹配文档的 SingleObservable[Document],或者如果没有与查询过滤器匹配的文档,则在不发出值的情况下完成。如果查询过滤匹配多个文档,则该方法将作用于第一个匹配项,其中 first 是指磁盘上的自然顺序,除非您在相应的选项对象中指定排序顺序。
查找并更新
要查找并更新一个文档,请使用 MongoCollection 类的 findOneAndUpdate() 方法。
例子
以下示例使用 findOneAndUpdate() 方法查找 color字段值为 "green" 的文档。然后,将该文档中的 food字段更新为 "pizza"。
该示例还使用FindOneAndUpdateOptions实例来指定以下选项:
指定更新或插入,如果没有与查询匹配的文档,则会插入查询筛选器指定的文档。
在MongoDB实例上将此操作的最长执行时间设置为 5 秒。如果操作花费的时间更长,
findOneAndUpdate()方法会通过观察器的onError()回调发出MongoExecutionTimeoutException。
val filter = equal("color", "green") val update = set("food", "pizza") val options = new FindOneAndUpdateOptions() .upsert(true) .maxTime(5, TimeUnit.SECONDS) val observable: SingleObservable[Document] = collection.findOneAndUpdate(filter, update, options) observable.subscribe(new Observer[Document] { override def onNext(doc: Document): Unit = println(doc.toJson()) override def onError(e: Throwable): Unit = println(s"Failed: ${e.getMessage}") override def onComplete(): Unit = println("Completed") })
{"_id": {"$oid": "1"}, "color": "green", "food": "pizza"} Completed
有关本节中使用的方法和类的更多信息,请参阅以下API文档:
查找并替换
要查找和替换一个文档,请使用 MongoCollection 类的 findOneAndReplace() 方法。
例子
以下示例使用 findOneAndReplace() 方法查找 color字段值为 "green" 的文档。它将匹配的文档替换为
{"music": "classical", "color": "green"}
该示例还使用 FindOneAndReplaceOptions实例指定返回的文档应处于替换操作后的状态。
val replaceFilter = equal("color", "green") val replacement = Document( "music" -> "classical", "color" -> "green" ) val replaceOptions = new FindOneAndReplaceOptions() .returnDocument(ReturnDocument.AFTER) val replaceObservable: SingleObservable[Document] = collection.findOneAndReplace(replaceFilter, replacement, replaceOptions) replaceObservable.subscribe(new Observer[Document] { override def onNext(doc: Document): Unit = println(doc.toJson()) override def onError(e: Throwable): Unit = println(s"Failed: ${e.getMessage}") override def onComplete(): Unit = println("Completed") })
{"_id": {"$oid": "1"}, "music": "classical", "color": "green"} Completed
有关本节中使用的方法和类的更多信息,请参阅以下API文档:
查找和删除
要查找并删除一个文档,请使用 MongoCollection 类的 findOneAndDelete() 方法。
例子
以下示例使用 findOneAndDelete() 方法查找并删除_id字段中具有最大值的文档。
该示例使用FindOneAndDeleteOptions实例指定对_id字段进行降序排序。
val deleteOptions = new FindOneAndDeleteOptions() .sort(descending("_id")) val deleteObservable: SingleObservable[Document] = collection.findOneAndDelete(empty(), deleteOptions) deleteObservable.subscribe(new Observer[Document] { override def onNext(doc: Document): Unit = println(doc.toJson()) override def onError(e: Throwable): Unit = println(s"Failed: ${e.getMessage}") override def onComplete(): Unit = println("Completed") })
{"_id": {"$oid": "2"}, "food": "pear", "color": "yellow"} Completed
有关本节中使用的方法和类的更多信息,请参阅以下API文档:
避免争用条件
通过以原子方式执行多个操作,复合操作可帮助您避免争用情况。
对于本节中的示例,假设您运行一家只有一个房间的酒店。您使用一个小型Scala程序来登记客人进入房间。
以下文档代表酒店房间:
{"_id": {"$oid": "1"}, "guest": null, "room": "Blue Room", "reserved": false}
具有竞争条件的示例
以下示例使用 bookARoomUnsafe() 函数为客人登记房间。该函数分别执行查找和更新操作。
def bookARoomUnsafe(guestName: String): Unit = { val availableFilter = equal("reserved", false) val findResults = Await.result( hotelCollection.find(availableFilter).first().toFuture(), Duration(10, TimeUnit.SECONDS) ) if (Option(findResults).isEmpty) { println(s"Sorry, we are booked, $guestName") return } val room = findResults val roomName = room.getString("room") println(s"You got the $roomName, $guestName") val reserveUpdate = combine( set("reserved", true), set("guest", guestName) ) val roomFilter = equal("_id", room.get("_id").get) Await.result( hotelCollection.updateOne(roomFilter, reserveUpdate).toFuture(), Duration(10, TimeUnit.SECONDS) ) }
假设两位客人(Jan 和 Pat)尝试同时预订同一房间。
Jan 会看到以下输出:
You got the Blue Room, Jan
Pat 看到以下输出:
You got the Blue Room, Pat
当您查看数据库时,您会看到以下文档:
{"_id": {"$oid": "1"}, "guest": "Jan", "room": "Blue Room", "reserved": true}
您的应用程序告诉 Pat 和 Jan 他们已预订房间,但数据库显示仅为 Jan 预订的房间。以下是MongoDB执行的操作顺序:
查找并返回 Jan 的空房间
查找并返回 Pat 的空房间
使用 Pat 的预订更新房间
使用 Jan 的预订更新房间
尽管 Pat 暂时保留了房间,但 Jan 的更新操作覆盖了 Pat 的保留,因为它最后运行。
无竞争条件的示例
以下示例使用 bookARoomSafe() 函数为客人登记房间。该函数使用 findOneAndUpdate() 将查找和更新操作作为单个原子操作来执行。
def bookARoomSafe(guestName: String): Unit = { val reserveUpdate = combine( set("reserved", true), set("guest", guestName) ) val availableFilter = equal("reserved", false) val room: Document = Await.result( hotelCollection.findOneAndUpdate(availableFilter, reserveUpdate).toFuture(), Duration(10, TimeUnit.SECONDS) ) if (Option(room).isEmpty) { println(s"Sorry, we are booked, $guestName") return } val roomName = room.getString("room") println(s"You got the $roomName, $guestName") }
假设两位客人(Jan 和 Pat)尝试同时预订同一房间。
Jan 会看到以下输出:
You got the Blue Room, Jan
Pat 看到以下输出:
Sorry, we are booked, Pat
当您查看数据库时,您会看到以下文档:
{"_id": {"$oid": "1"}, "guest": "Jan", "room": "Blue Room", "reserved": true}
Jan 保留了房间,Pat 收到了没有可用房间的正确消息。以下是MongoDB执行的操作顺序:
为 Jan 查找并预订空房间
尝试为帕特寻找并预订空房间
找不到空房间并在不发出值的情况下完成
由于 findOneAndUpdate() 以原子方式执行查找和更新,因此在 Jan 的预订完成后,Pat 的操作无法将房间读取为可用。
重要
写锁
在复合操作期间, MongoDB实例会在您正在修改的文档上放置写锁(write lock)。
有关本节中使用的方法和类的更多信息,请参阅以下API文档: