文档菜单
文档首页
/ / /
Java 同步驱动程序
/ /

复合操作

在本页面上

  • 概述
  • 如何使用复合操作
  • 查找和更新
  • 查找和替换
  • 查找和删除
  • 避免竞争条件
  • 有竞争条件的示例
  • 无竞争条件的示例

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

复合操作由一个读写操作作为一个原子操作执行。原子操作是指要么完全完成,要么完全不完成的操作。原子操作不能部分完成。

原子操作可以帮助您避免代码中的竞态条件。竞态条件发生在代码的行为依赖于不可控事件的顺序时。

MongoDB支持以下复合操作

  • 查找并更新一个文档

  • 查找并替换一个文档

  • 查找并删除一个文档

如果您必须原子地执行更复杂的任务,例如读取和写入多个文档,请使用事务。事务是MongoDB和其他数据库的一个功能,允许您将任意一系列数据库命令定义为原子操作。

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

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

本节展示了如何使用MongoDB Java Driver中的每个复合操作。

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

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

以下示例的完整代码可在GitHub上在此处找到。

注意

写入之前或之后?

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

要查找并更新一个文档,请使用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}

此示例的完整代码可在 GitHub 上找到。

假设我们的应用程序使用此 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类

返回

查询