Docs 菜单
Docs 主页
/ / /
Kotlin 协程
/ /

复合运算符

在此页面上

  • Overview
  • 如何使用复合运算
  • 查找并更新
  • 查找并替换
  • 查找和删除
  • 避免争用条件
  • 具有竞争条件的示例
  • 无竞争条件的示例

在本指南中,您可以了解如何使用 MongoDB Kotlin 驱动程序执行复合操作

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

原子操作可以帮助您避免代码中出现竞争条件。当代码的行为依赖于不可控的事件顺序时,就会出现竞争条件。

MongoDB 支持以下复合操作:

  • 查找并更新一个文档

  • 查找并替换一个文档

  • 查找并删除一个文档

如果您需要自动执行更复杂的任务,例如读取和写入多个文档,请使用事务。事务是 MongoDB 和其他数据库的一项功能,可让您将任意数据库命令序列定义为原子操作。

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

有关事务的更多信息,请参阅MongoDB 手册中的事务条目。

本节介绍如何将每个复合操作与 MongoDB Kotlin 驱动程序结合使用。

以下示例使用包含这两个样本文档的集合。

{"_id": 1, "food": "donut", "color": "green"}
{"_id": 2, "food": "pear", "color": "yellow"}

此数据使用以下 Kotlin 数据类进行建模:

data class FoodOrder(
@BsonId val id: Int,
val food: String,
val color: String
)

注意

写入之前还是之后?

默认情况下,每个复合操作都会以执行写入操作之前的状态返回找到的文档。 您可以使用与复合操作对应的选项类,在写入操作后的状态下检索找到的文档。 您可以在下面的“查找和替换”示例中查看此配置的示例。

要查找并更新一个文档,请使用MongoCollection类的 findOneAndUpdate()方法。 findOneAndUpdate()方法返回找到的文档;如果没有与查询匹配的文档,则返回null

以下示例使用findOneAndUpdate()方法查找color字段设置为"green"的文档,并将该文档中的food字段更新为"pizza"

该示例还使用FindOneAndUpdateOptions实例来指定以下选项:

  • 指定更新或插入,如果没有与查询匹配的文档,则会插入查询筛选器指定的文档。

  • 在 MongoDB 实例上将此操作的最长执行时间设置为 5 秒。 如果操作花费的时间更长, findOneAndUpdate()方法将抛出MongoExecutionTimeoutException

val filter = Filters.eq(FoodOrder::color.name, "green")
val update = Updates.set(FoodOrder::food.name, "pizza")
val options = FindOneAndUpdateOptions()
.upsert(true)
.maxTime(5, TimeUnit.SECONDS)
/* The result variable contains your document in the
state before your update operation is performed
or null if the document was inserted due to upsert
being true */
val result = collection.findOneAndUpdate(filter, update, options)
println(result)
FoodOrder(id=1, food=donut, color=green)

有关Projections类的更多信息,请参阅有关投影构建指南指南。

有关更新或插入操作的更多信息,请参阅我们的更新或插入指南。

有关本节中提到的方法和类的详情,请参阅以下 API 文档:

要查找并替换一个文档,请使用MongoCollection类的findOneAndReplace()方法。findOneAndReplace()方法返回找到的文档;如果没有与查询匹配的文档,则返回null

以下示例使用findOneAndReplace()方法查找color字段设置为"green"的文档,并将其替换为以下文档:

{"music": "classical", "color": "green"}

该示例还使用FindOneAndReplaceOptions实例来指定返回的文档应处于替换操作之后的状态。

data class Music(
@BsonId val id: Int,
val music: String,
val color: String
)
val filter = Filters.eq(FoodOrder::color.name, "green")
val replace = Music(1, "classical", "green")
val options = FindOneAndReplaceOptions()
.returnDocument(ReturnDocument.AFTER)
val result = collection.withDocumentClass<Music>().findOneAndReplace(filter, replace, options)
println(result)
Music(id=1, music=classical, color=green)

有关本节中提到的方法和类的详情,请参阅以下 API 文档:

要查找并删除一个文档,请使用MongoCollection类的findOneAndDelete()方法。findOneAndDelete()方法返回找到的文档;如果没有与查询匹配的文档,则返回null

以下示例使用findOneAndDelete()方法查找并删除_id字段中具有最大值的文档。

该示例使用FindOneAndDeleteOptions实例指定对_id字段进行降序排序。

val sort = Sorts.descending("_id")
val filter = Filters.empty()
val options = FindOneAndDeleteOptions().sort(sort)
val result = collection.findOneAndDelete(filter, options)
println(result)
FoodOrder(id=2, food=pear, color=yellow)

有关 Sorts 类的更多信息,请参阅我们的 Sorts 构建器指南

有关本节中提到的方法和类的详情,请参阅以下 API 文档:

在本节中,我们将探讨两个示例。 第一个示例包含竞争条件,第二个示例使用复合操作来避免第一个示例中出现的竞争条件。

对于这两个示例,假设我们经营一家只有一个房间的酒店,并且有一个 Kotlin 小程序来帮助我们向客人结账这个房间。

MongoDB 中的以下文档代表房间:

{"_id": 1, "guest": null, "room": "Blue Room", "reserved": false}

此数据使用以下 Kotlin 数据类进行建模:

data class HotelRoom(
@BsonId val id: Int,
val guest: String? = null,
val room: String,
val reserved: Boolean = false
)

假设我们的应用程序使用此bookARoomUnsafe方法向客人签出房间:

suspend fun bookARoomUnsafe(guestName: String) {
val filter = Filters.eq("reserved", false)
val myRoom = hotelCollection.find(filter).firstOrNull()
if (myRoom == null) {
println("Sorry, we are booked, $guestName")
return
}
val myRoomName = myRoom.room
println("You got the $myRoomName, $guestName")
val update = Updates.combine(Updates.set("reserved", true), Updates.set("guest", guestName))
val roomFilter = Filters.eq("_id", myRoom.id)
hotelCollection.updateOne(roomFilter, update)
}

假设两位不同的客人(Jan 和 Pat)尝试同时使用此方法预订房间。

Jan 会看到以下输出:

You got the Blue Room, Jan

Pat 看到了以下输出:

You got the Blue Room, Pat

当我们查看数据库时,会看到以下内容:

{"_id": 1, "guest": "Jan", "room": "Blue Room", "reserved": false}

帕特会不高兴的。 当帕特 (Pat) 出现在我们酒店时,简 (Jan) 正在使用她的房间。 Go了什么问题?

以下是从 MongoDB 实例的角度来看所发生事件的顺序:

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

  2. 查找并返回一个空房间给帕特。

  3. 更新为 Pat 预订的房间。

  4. 将 Jan 的房间更新为“已预订”。

请注意,Pat 曾短暂保留了房间,但由于 Jan 的更新操作是最后一个执行的,因此我们的文档将"Jan"作为来宾。

让我们使用复合操作来避免竞争条件,并始终为用户提供正确的消息。

suspend fun bookARoomSafe(guestName: String) {
val update = Updates.combine(
Updates.set(HotelRoom::reserved.name, true),
Updates.set(HotelRoom::guest.name, guestName)
)
val filter = Filters.eq("reserved", false)
val myRoom = hotelCollection.findOneAndUpdate(filter, update)
if (myRoom == null) {
println("Sorry, we are booked, $guestName")
return
}
val myRoomName = myRoom.room
println("You got the $myRoomName, $guestName")
}

假设两位不同的客人(Jan 和 Pat)尝试同时使用此方法预订房间。

Jan 会看到以下输出:

You got the Blue Room, Jan

Pat 看到了以下输出:

Sorry, we are booked, Pat

当我们查看数据库时,会看到以下内容:

{"_id": 1, "guest": "Jan", "room": "Blue Room", "reserved": false}

Pat 收到了正确的消息。虽然她可能会因为没有预订成功而感到难过,但至少她知道不要前往我们的酒店。

以下是从 MongoDB 实例的角度来看所发生事件的顺序:

  1. 为 Jan 找到并预订一个空房间。

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

  3. 当没有剩余房间时,返回null

重要

写锁

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

有关Updates类的信息,请参阅我们的更新构建器指南。

有关Filters类的更多信息,请参阅我们的筛选器构建器指南。

有关findOneAndUpdate() 方法的更多信息,请参阅 MongoCollection 类的 API 文档。

后退

指定查询