复合操作
概述
在本指南中,您可以学习如何使用MongoDB Java驱动程序执行复合操作。
复合操作由一个读写操作作为一个原子操作执行。原子操作是指要么完全完成,要么完全不完成的操作。原子操作不能部分完成。
原子操作可以帮助您避免代码中的竞态条件。竞态条件发生在代码的行为依赖于不可控事件的顺序时。
MongoDB支持以下复合操作
查找并更新一个文档
查找并替换一个文档
查找并删除一个文档
如果您必须原子地执行更复杂的任务,例如读取和写入多个文档,请使用事务。事务是MongoDB和其他数据库的一个功能,允许您将任意一系列数据库命令定义为原子操作。
有关原子操作和原子性的更多信息,请参阅MongoDB手册中关于原子性和事务的条目.
有关事务的更多信息,请参阅MongoDB手册中关于事务的条目。
如何使用复合操作
本节展示了如何使用MongoDB Java Driver中的每个复合操作。
以下示例使用包含这两个样本文档的集合。
{"_id": 1, "food": "donut", "color": "green"} {"_id": 2, "food": "pear", "color": "yellow"}
查找并更新
要查找并更新一个文档,请使用findOneAndUpdate()
方法,该方法是MongoCollection
类的一部分。如果查询没有匹配的文档,findOneAndUpdate()
方法将返回您的找到的文档或null
。
示例:
以下示例使用 findOneAndUpdate()
方法查找字段 color
设置为 "green"
的文档,并将该文档中的 food
字段更新为 "pizza"
。
示例还使用 FindOneAndUpdateOptions
实例来指定以下选项
使用投影排除找到的文档中的
_id
字段。指定一个 upsert,如果查询没有匹配的文档,则插入由查询过滤器指定的文档。
为 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
类的更多信息,请参阅我们的投影构建器指南.
有关 upsert 操作的更多信息,请参阅我们的 upsert 指南。
有关本节中提到的方法和类的更多信息,请参阅以下 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 会不高兴。当 Pat 来到我们的酒店时,Jan 将占用她的房间。出了什么问题?
以下是我们的 MongoDB 实例视角下发生的事件序列
为 Jan 查找并返回一个空房间
为 Pat 查找并返回一个空房间
将房间更新为 Pat 预订
将房间更新为 Jan 预订
请注意,在很短的时间内 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找到一间空房间并预订。
尝试为Pat找到一间空房间并预订。由于没有空房间,返回
null
。
重要
写入锁
您的MongoDB实例在复合操作期间会对您正在修改的文档执行写入锁。
有关Updates
类的信息,请参阅我们的Updates构建器指南。
有关Filters
类的更多信息,请参阅我们的Filters构建器指南。
有关findOneAndUpdate()
方法的信息,请参阅MongoCollection类