对于 AI 代理:可在 https://www.mongodb.com/zh-cn/docs/llms.txt 获取文档索引—通过在任何 URL 路径后添加 .md 可获取所有页面的 Markdown 版本。
Docs 菜单

复合运算符

在本指南中,您可以学习;了解如何使用Scala驾驶员执行复合操作。

复合操作由作为原子操作执行的读取操作和写入操作组成。原子操作是指要么完全完成,要么根本未完成的操作。

原子操作可以帮助您避免代码中出现争用情况。当代码的行为取决于不可控事件的顺序时,就会出现争用情况。

MongoDB 支持以下复合操作:

  • 查找并更新一个文档

  • 查找并替换一个文档

  • 查找并删除一个文档

有关原子操作和原子性的更多信息,请参阅MongoDB Server手册中的原子性和事务。

提示

事务

如果您需要自动执行更复杂的任务,例如读取和写入多个文档,请使用事务。事务允许您将一系列数据库命令作为原子操作来执行。有关事务的更多信息,请参阅MongoDB Server手册中的事务。

本节介绍如何使用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执行的操作顺序:

  1. 查找并返回 Jan 的空房间

  2. 查找并返回 Pat 的空房间

  3. 使用 Pat 的预订更新房间

  4. 使用 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执行的操作顺序:

  1. 为 Jan 查找并预订空房间

  2. 尝试为帕特寻找并预订空房间

  3. 找不到空房间并在不发出值的情况下完成

由于 findOneAndUpdate() 以原子方式执行查找和更新,因此在 Jan 的预订完成后,Pat 的操作无法将房间读取为可用。

重要

写锁

在复合操作期间, MongoDB实例会在您正在修改的文档上放置写锁(write lock)。

有关本节中使用的方法和类的更多信息,请参阅以下API文档: