Docs 菜单

Docs 主页开发应用程序MongoDB 驱动程序Java (Sync) 驱动程序

复合运算符

在此页面上

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

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

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

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

MongoDB 支持以下复合操作:

  • 查找并更新一个文档

  • 查找并替换一个文档

  • 查找并删除一个文档

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

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

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

本节介绍如何通过 MongoDB Java 驱动程序使用每个复合操作。

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

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

以下示例的完整代码可在 Github 上找到。

注意

写入之前还是之后?

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

要查找并更新一个文档,请使用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类的更多信息,请参阅我们的排序构建器指南。

有关本节中提到的方法和类的详情,请参阅以下 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) 出现在我们酒店时,简 (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 文档。

← 指定查询