复合运算符
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( 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( 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( 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 实例的角度来看所发生事件的顺序:
查找并返回 Jan 的空房间。
查找并返回一个空房间给帕特。
更新为 Pat 预订的房间。
将 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 实例的角度来看所发生事件的顺序:
为 Jan 找到并预订一个空房间。
尝试为帕特找到并预订空房间。
当没有剩余房间时,返回
null
。
重要
写锁
在复合操作期间,MongoDB 实例会在您正在修改的文档上放置写锁。
有关Updates
类的信息,请参阅我们的更新构建器指南。
有关Filters
类的更多信息,请参阅我们的筛选器构建器指南。
有关findOneAndUpdate()
方法的更多信息,请参阅 MongoCollection 类的 API 文档。