复合运算符
Overview
在本指南中,您可以了解如何使用 MongoDB Java 驱动程序执行复合操作。
复合操作由作为一个原子操作执行的读取和写入操作组成。 原子操作是指要么完全完成,要么根本未完成的操作。 原子操作无法部分完成。
原子操作可以帮助您避免代码中出现竞争条件。当代码的行为依赖于不可控的事件顺序时,就会出现竞争条件。
MongoDB 支持以下复合操作:
查找并更新一个文档
查找并替换一个文档
查找并删除一个文档
如果您需要自动执行更复杂的任务,例如读取和写入多个文档,请使用事务。事务是 MongoDB 和其他数据库的一项功能,可让您将任意数据库命令序列定义为原子操作。
有关原子操作和原子性的更多信息,请参阅MongoDB 手册中有关原子性和事务的条目。
有关事务的更多信息,请参阅 MongoDB 手册中的事务条目。
如何使用复合运算
本节介绍如何通过 MongoDB Java 驱动程序使用每个复合操作。
以下示例使用包含这两个样本文档的集合。
{"_id": 1, "food": "donut", "color": "green"} {"_id": 2, "food": "pear", "color": "yellow"}
注意
写入之前还是之后?
默认情况下,每个复合操作都会以执行写入操作之前的状态返回找到的文档。 您可以使用与复合操作对应的选项类,在写入操作后的状态下检索找到的文档。 您可以在下面的“查找和替换”示例中查看此配置的示例。
查找并更新
要查找并更新一个文档,请使用MongoCollection
类的 findOneAndUpdate()
方法。 findOneAndUpdate()
方法返回找到的文档;如果没有与查询匹配的文档,则返回null
。
例子
以下示例使用findOneAndUpdate()
方法查找color
字段设置为"green"
的文档,并将该文档中的food
字段更新为"pizza"
。
该示例还使用FindOneAndUpdateOptions
实例来指定以下选项:
使用投影从找到的文档中排除
_id
字段。指定更新或插入,如果没有与查询匹配的文档,则会插入查询筛选器指定的文档。
在 MongoDB 实例上将此操作的最长执行时间设置为 5 秒。 如果操作花费的时间更长,
findOneAndUpdate()
方法将抛出MongoExecutionTimeoutException
。
// <MongoCollection set up code here> // Creates a projection to exclude the "_id" field from the retrieved documents Bson projection = Projections.excludeId(); // Creates a filter to match documents with a "color" value of "green" Bson filter = Filters.eq("color", "green"); // Creates an update document to set the value of "food" to "pizza" Bson update = Updates.set("food", "pizza"); // Defines options that specify projected fields, permit an upsert and limit execution time FindOneAndUpdateOptions options = new FindOneAndUpdateOptions(). projection(projection). upsert(true). maxTime(5, TimeUnit.SECONDS); // Updates the first matching document with the content of the update document, applying the specified options Document result = collection.findOneAndUpdate(filter, update, options); // Prints the matched document in its state before the operation System.out.println(result.toJson());
上述代码的输出应如下所示:
{"food": "pizza", "color": "green"}
有关Projections
类的更多信息,请参阅有关投影构建指南的指南。
有关更新或插入操作的更多信息,请参阅我们的更新或插入指南。
有关本节中提到的方法和类的详情,请参阅以下 API 文档:
查找并替换
要查找并替换一个文档,请使用MongoCollection
类的findOneAndReplace()
方法。findOneAndReplace()
方法返回找到的文档;如果没有与查询匹配的文档,则返回null
。
例子
以下示例使用findOneAndReplace()
方法查找color
字段设置为"green"
的文档,并将其替换为以下文档:
{"music": "classical", "color": "green"}
该示例还使用FindOneAndReplaceOptions
实例来指定返回的文档应处于替换操作之后的状态。
// <MongoCollection set up code here> // Creates instructions to replace the matching document with a new document Bson filter = Filters.eq("color", "green"); Document replace = new Document("music", "classical").append("color", "green"); // Defines options specifying that the operation should return a document in its post-operation state FindOneAndReplaceOptions options = new FindOneAndReplaceOptions(). returnDocument(ReturnDocument.AFTER); // Atomically finds and replaces the matching document and prints the replacement document Document result = collection.findOneAndReplace(filter, replace, options); System.out.println(result.toJson());
上述代码的输出应如下所示:
{"_id": 1, "music": "classical", "color": "green"}
有关本节中提到的方法和类的详情,请参阅以下 API 文档:
查找和删除
要查找并删除一个文档,请使用MongoCollection
类的findOneAndDelete()
方法。findOneAndDelete()
方法返回找到的文档;如果没有与查询匹配的文档,则返回null
。
例子
以下示例使用findOneAndDelete()
方法查找并删除_id
字段中具有最大值的文档。
该示例使用FindOneAndDeleteOptions
实例指定对_id
字段进行降序排序。
// <MongoCollection set up code here> Bson sort = Sorts.descending("_id"); // Creates an empty filter to match all documents in the collection Bson filter = Filters.empty(); // Defines options that specify a descending sort on the "_id" field FindOneAndDeleteOptions options = new FindOneAndDeleteOptions(). sort(sort); // Deletes the document containing the highest "_id" value and prints the deleted document Document result = collection.findOneAndDelete(filter, options); System.out.println(result.toJson());
上述代码的输出应如下所示:
{"_id": 2, "food": "pear", "color": "yellow"}
有关 Sorts
类的更多信息,请参阅我们的 Sorts 构建器指南。
有关本节中提到的方法和类的详情,请参阅以下 API 文档:
避免争用条件
在本节中,我们将探讨两个示例。 第一个示例包含竞争条件,第二个示例使用复合操作来避免第一个示例中出现的竞争条件。
对于这两个示例,假设我们经营一家只有一个房间的酒店,并且有一个小型 Java 程序来帮助我们向客人结账这个房间。
MongoDB 中的以下文档代表房间:
{"_id": 1, "guest": null, "room": "Blue Room", "reserved": false}
具有竞争条件的示例
假设我们的应用程序使用此bookARoom
方法向客人签出房间:
public void bookARoom() { // Creates a filter to match documents representing available rooms Bson filter = Filters.eq("reserved", false); // Retrieves a document that represents the first available room Document myRoom = this.collection.find(filter).first(); // Prints a message if no documents representing available rooms are found if (myRoom == null){ System.out.println("Sorry, we are booked " + this.guest); return; } String myRoomName = myRoom.getString("room"); // Prints a message that guest that successfully booked the room System.out.println("You got the " + myRoomName + " " + this.guest); // Creates an update document to mark a room as reserved Bson update = Updates.combine(Updates.set("reserved", true), Updates.set("guest", guest)); // Creates a filter that matches the "_id" field of the first available room Bson roomFilter = Filters.eq("_id", myRoom.get("_id", Integer.class)); // Updates the first matching document to mark it as reserved this.collection.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": true}
帕特会不高兴的。 当帕特 (Pat) 出现在我们酒店时,简 (Jan) 正在使用她的房间。 Go了什么问题?
以下是从 MongoDB 实例的角度来看所发生事件的顺序:
查找并返回 Jan 的空房间
查找并返回一个空房间给 Pat
将 Pat 的房间更新为“已预订”
更新 1 月份预订的房间
请注意,Pat 曾短暂保留了房间,但由于 Jan 的更新操作是最后一个执行的,因此我们的文档将"Jan"
作为来宾。
无竞争条件的示例
让我们使用复合操作来避免竞争条件,并始终为用户提供正确的消息。
public void bookARoom(){ // Creates an update document to mark a room as reserved Bson update = Updates.combine(Updates.set("reserved", true), Updates.set("guest", guest)); // Creates a filter to match a document representing an available room Bson filter = Filters.eq("reserved", false); // Updates the first document that matches the filter to mark it as reserved Document myRoom = this.collection.findOneAndUpdate(filter, update); // Prints a message when there are no available rooms if (myRoom == null){ System.out.println("Sorry, we are booked " + this.guest); return; } // Prints the name of the guest that successfully booked the room String myRoomName = myRoom.getString("room"); System.out.println("You got the " + myRoomName + " " + this.guest); }
假设两位不同的客人(Jan 和 Pat)尝试同时使用此方法预订房间。
Jan 会看到以下输出:
You got the Blue Room Jan
Pat 看到了以下输出:
Sorry, we are booked Pat
当我们查看数据库时,会看到以下内容:
{"_id":1, "guest":"Jan", "room":"Blue Room", "reserved":true}
Pat 收到了正确的消息。虽然她可能会因为没有预订成功而感到难过,但至少她知道不要前往我们的酒店。
以下是从 MongoDB 实例的角度来看所发生事件的顺序:
为 Jan 找到并预订一个空房间。
尝试为帕特找到并预订空房间。 由于没有剩余房间,因此返回
null
。
重要
写锁
在复合操作期间,MongoDB 实例会在您正在修改的文档上放置写锁。
有关Updates
类的信息,请参阅我们的更新构建器指南。
有关Filters
类的更多信息,请参阅我们的筛选器构建器指南。
有关findOneAndUpdate()
方法的更多信息,请参阅 MongoCollection 类的 API 文档。