对于 AI 代理:可在 https://www.mongodb.com/zh-cn/docs/llms.txt 获取文档索引—通过在任何 URL 路径后添加 .md 可获取所有页面的 Markdown 版本。

复合运算符

在本指南中,您可以了解如何使用 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字段。

  • 指定更新或插入,如果没有与查询匹配的文档,则会插入查询筛选器指定的文档。

// <MongoCollection set up code here> // Creates a projection to exclude the "_id" field from the retrieved documentsBson 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 and permit upsertsFindOneAndUpdateOptions options = new FindOneAndUpdateOptions() .projection(projection) .upsert(true); // Updates the first matching document with the content of the update document, applying the specified optionsDocument result = collection.findOneAndUpdate(filter, update, options); // Prints the matched document in its state before the operationSystem.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 documentBson 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 stateFindOneAndReplaceOptions options = new FindOneAndReplaceOptions(). returnDocument(ReturnDocument.AFTER); // Atomically finds and replaces the matching document and prints the replacement documentDocument 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 collectionBson filter = Filters.empty(); // Defines options that specify a descending sort on the "_id" fieldFindOneAndDeleteOptions options = new FindOneAndDeleteOptions(). sort(sort); // Deletes the document containing the highest "_id" value and prints the deleted documentDocument 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文档。