

本文属于机器翻译版本。若本译文内容与英语原文存在差异，则一律以英文原文为准。

# Amazon DocumentDB Java 编程指南
DocumentDB Java 编程指南

本全面指南详细介绍了如何使用 MongoDB 的 Java 驱动程序处理 Amazon DocumentDB，涵盖数据库操作与管理的基本方面。

**Topics**
+ [

## 简介
](#java-pg-intro)
+ [

## 先决条件
](#java-pg-prereqs)
+ [数据模型](#java-pg-data-models)
+ [使用 Java 驱动程序进行连接](java-pg-connect-mongo-driver.md)
+ [使用 Java 执行 CRUD 操作](java-crud-operations.md)
+ [使用 Java 进行索引管理](index-management-java.md)
+ [事件驱动型编程](event-driven-programming.md)

## 简介


本指南从连接开始，阐述如何使用 MongoDB Java 驱动程序建立到 DocumentDB 集群的安全连接。它详细介绍了连接字符串组件、 SSL/TLS 实现和各种连接选项，包括 IAM 身份验证和连接池，以及强大的错误处理策略。

在 CRUD（创建、读取、更新、删除）操作章节中，本指南全面介绍了文档操作，演示了如何使用单个和批量操作创建、读取、更新和删除文档。阐释了筛选条件、查询和各种操作选项的用法，同时强调了错误处理和实现重试逻辑以提高可靠性的最佳实践。本指南还广泛介绍了索引管理，详细介绍了不同索引类型（包括单字段索引、复合索引、稀疏索引和文本索引）的创建和维护。阐释了如何通过正确选择索引和使用 `explain()` 函数分析查询执行计划，来优化查询性能。

最后一节重点介绍使用 Amazon DocumentDB 的变更流进行事件驱动型编程，演示如何在 Java 应用程序中实现实时数据变更监控。介绍了变更流光标的实现、用于持续操作的恢复令牌的处理以及用于历史数据处理的基于时间的操作。本指南中提供了实用的代码示例和最佳实践，这使其成为您使用 Amazon DocumentDB 构建强大 Java 应用程序的宝贵资源。

## 先决条件


在开始之前，请确保您具有以下各项：
+ 具有已配置文档数据库集群的 Amazon 账户。请参阅此篇[入门博客文章](https://www.amazonaws.cn/blogs/database/part-1-getting-started-with-amazon-documentdb-using-amazon-ec2/)，了解 DocumentDB 集群设置。
+ 已安装 Java 开发工具包（JDK）（在本指南中，我们将使用 [Amazon Corretto 21](https://docs.amazonaws.cn/corretto/latest/corretto-21-ug/downloads-list.html)）。
+ 用于依赖项管理的 Maven。

## 本指南的数据模型
数据模型

本指南中的所有示例代码都假设连接到具有 “RestaurantsProgGuideData” 集合的 “” 测试数据库。本指南中的所有示例代码都适用于餐厅列表系统，以下是该系统中文档的示例：

```
{
    "_id": "ab6ad8f119b5bca3efa2c7ae",
    "restaurantId": "REST-CRT9BL",
    "name": "Thai Curry Palace",
    "description": "Amazing Restaurant, must visit",
    "cuisine": "Thai",
    "address": {
        "street": "914 Park Street",
        "city": "Bryan",
        "state": "AL",
        "zipCode": "96865",
        "location": {
            "type": "Point",
            "coordinates": [-25.4619, 8.389]
        }
    },
    "contact": {
        "phone": "(669) 915-9056 x6657"
    },
    "rating": {
        "average": 3.4,
        "totalReviews": 275
    },
    "priceRange": "$",
    "menu": [{
        "category": "Appetizers",
        "items": [{
            "name": "Buffalo Chicken Wings",
            "price": 13.42
        }]
    }],
    "features": [
        "Private Dining"
    ],
    "isActive": false“ michelin”: {“
        star”: 3,
        “ranking_years”: 4
    }
}
```

所有显示 CRUD、索引管理和事件驱动型编程的代码示例都假设您已具有一个 [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/MongoClient.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/MongoClient.html) 对象 `dbClient`、一个 [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/MongoDatabase.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/MongoDatabase.html) 对象 `connectionDB` 和一个 [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/MongoCollection.html#find()](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/MongoCollection.html#find()) 对象 `collection`。

**注意**  
本指南中的所有代码示例均已使用 MongoDB Java 驱动程序版本 5.3.0 进行了测试。

# 使用 MongoDB Java 驱动程序连接到 Amazon DocumentDB
使用 Java 驱动程序进行连接

本节提供使用 Java 驱动程序连接亚马逊文档数据库的 step-by-step指南。这将使您开始将 DocumentDB 集成到 Java 应用程序中。

**Topics**
+ [

## 第 1 步：设置您的 项目
](#step1-set-up)
+ [

## 步骤 2：创建连接字符串
](#step2-create-connection-string)
+ [

## 步骤 3：编写连接代码
](#step3-write-connect-code)
+ [

## 步骤 4：处理连接异常
](#step4-handle-connect-exceptions)
+ [

## 步骤 5：运行代码
](#step5-running-code)
+ [

## 连接最佳实践
](#java-connect-best-practices)

## 第 1 步：设置您的 项目
设置您的项目

1. 使用 Maven 创建 Java 项目：

   ```
   mvn archetype:generate -DgroupId=com.docdb.guide -DartifactId=my-docdb-project -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false
   ```

1. 将 MongoDB Java 驱动程序作为项目的依赖项添加到您的“pom.xml”文件中：

   ```
   <dependency>
       <groupId>org.mongodb</groupId>
       <artifactId>mongodb-driver-sync</artifactId> 
       <version>5.3.0</version> 
   </dependency>
   ```

## 步骤 2：创建连接字符串
创建连接字符串

Amazon DocumentDB 连接字符串对于在您的应用程序与 DocumentDB 集群之间建立连接至关重要。此字符串封装了重要信息，例如集群端点、端口、身份验证详细信息以及各种连接选项。要构建 DocumentDB 连接字符串，通常需从基本格式开始：

```
"mongodb://username:password@cluster-endpoint:port/?[connection options]"
```

您需要将“用户名”和“密码”替换为您的实际凭证。你可以在中找到集群的终端节点和端口号 Amazon Web Services 管理控制台 ，也可以通过 Amazon CLI。要查找集群的集群端点，请参阅 [查找集群的端点](db-cluster-endpoints-find.md)。DocumentDB 的默认端口为 27017。

**连接字符串示例**
+ 使用传输中加密建立到 DocumentDB 的连接，并确保将读取请求发送到只读副本，将写入请求发送到主项：

  ```
  "mongodb://username:password@cluster-endpoint:27017/?tls=true& 
     tlsCAFile=global-bundle.pem& 
     readPreference=secondaryPreferred&
     retryWrites=false"
  ```
+ 使用 IAM 身份验证建立到 DocumentDB 的连接：

  ```
  "mongodb://cluster-endpoint:27017/?tls=true& 
     tlsCAFile=global-bundle.pem& 
     readPreference=secondaryPreferred&
     retryWrites=false&
     authSource=%24external&
     authMechanism=MONGODB-AWS"
  ```

可用于连接字符串的不同选项如下：
+ [TLS 证书](#connection-string-tls)
+ [从只读副本读取](#connection-string-read-rep)
+ [写入关注和日志](#connection-string-write-journal)
+ [RetryWrites](#connection-string-retry-writes)
+ [IAM 身份验证](#connection-string-iam-auth)
+ [连接池](#connection-string-pool)
+ [连接超时参数](#connection-string-timeout)

### TLS 证书


**`tls=true|false`** – 此选项将启用或禁用传输层安全性协议（TLS）。默认情况下，在 Amazon DocumentDB 集群上启用传输中加密，因此，除非在集群级别禁用 TLS，否则此选项的值应为 `true`。

如果使用 TLS，在创建到 DocumentDB 集群的连接时，代码需要提供 SSL 证书。下载建立到集群的安全连接时所需要的证书：[https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem](https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem)。可通过两种方式使用 `global-bundle.pem` 文件。
+ **选项 1** – 从 `global-bundle.pem` 文件中提取所有证书，然后使用 Java 的 keytool 将其存储在 `.jks` 文件中，以便稍后在代码中使用。有关显示如何执行此操作的脚本，请参阅 [启用了 TLS 的情况下的连接](connect_programmatically.md#connect_programmatically-tls_enabled) 中的 Java 选项卡。
+ **选项 2** – 在代码中动态添加 `global-bundle.pem` 文件，构建内存密钥库，并在建立连接时使用 `SSLContext` 提供证书。

### 从只读副本读取


**`replicaSet=rs0&readPreference=secondaryPreferred`** – 指定这两个选项会将所有读取请求路由到只读副本，将写入请求路由到主实例。在连接字符串中使用 `replicaSet=rs0` 可使 MongoDB 驱动程序维护自动更新的集群拓扑视图，从而允许应用程序在添加或移除实例时维护当前节点配置的可见性。未提供这些选项或指定 `readPreference=primary` 则会将所有读取和写入请求发送到主实例。有关 `readPreference` 的更多选项，请参阅 [读取首选项选项](how-it-works.md#durability-consistency-isolation)。

### 写入关注和日志


写入关注决定了针对写入操作从数据库请求的确认级别。MongoDB 驱动程序提供了调整写入关注和日志文件的选项。Amazon DocumentDB 不希望您设置写入关注和日志，并且会忽略为 `w` 和 `j`（`writeConcern` 和 `journal`）发送的值。DocumentDB 始终使用 `writeConcern`：`majority` 和 `journal`：`true` 写入数据，因此在向客户端发送确认之前，写入会持久记录在大多数节点上。

### RetryWrites


**`retryWrites=false`** – DocumentDB 不支持可重试写入，因此应始终将该属性设置为 `false`。

### IAM 身份验证


**`authSource=%24external`和 `authMechanism=MONGODB-AWS`** — 这两个参数用于使用进行身份验证 Amazon Identity and Access Management。IAM 身份验证目前仅在基于实例的集群版本 5.0 中可用。有关更多信息，请参阅 [使用 IAM 身份进行身份验证](iam-identity-auth.md)。

### 连接池


以下选项可用于连接池：
+ **`maxPoolSize`** – 设置可以在池中创建的最大连接数。当所有连接都在使用中并且有新请求进来时，新请求需等待连接变为可用。MongoDB Java 驱动程序的默认值为 100。
+ **`minPoolSize`** – 表示应始终在池中维护的最小连接数。MongoDB Java 驱动程序的默认值为 0。
+ **`maxIdleTimeMS`** – 确定连接在被关闭和移除之前可以在池中保持空闲状态的时长。MongoDB Java 驱动程序的默认值为 100 毫秒。
+ **`waitQueueTimeoutMS`** – 配置当池达到其最大大小时，线程应等待连接变为可用的时长。如果连接未在此时间内变为可用，则抛出异常。MongoDB Java 驱动程序的默认值为 120000 毫秒（2 分钟）。

### 连接超时参数


超时是一种机制，用于限制操作或连接尝试在被认为失败之前所花费的时间量。以下超时参数可用于防止无限期等待及管理资源分配：
+ **`connectTimeoutMS`** – 配置驱动程序建立到集群的连接时的等待时长。默认值为 10,000 毫秒（10 秒）。
+ **`socketTimeoutMS`** – 指定驱动程序等待服务器对非写入操作的响应的时间长度。默认值为 0（无超时或无限期）。
+ **`serverSelectionTimeoutMS`** – 指定驱动程序在集群中查找可用服务器时的等待时长。此设置的默认值为 30 秒，足以在失效转移期间选择新的主实例。

## 步骤 3：编写连接代码
编写连接代码

以下代码示例显示了如何建立到 Amazon DocumentDB 的 TLS 连接：
+ 将创建 Java 的 [https://docs.oracle.com/javase/8/docs/api/java/security/KeyStore.html](https://docs.oracle.com/javase/8/docs/api/java/security/KeyStore.html) 和 [`SSLContext`>](https://docs.oracle.com/javase/8/docs/api/javax/net/ssl/SSLContext.html) 对象。
+ 还将创建 [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/MongoClientSettings.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/MongoClientSettings.html) 对象，方式是将其传递给 [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/ConnectionString.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/ConnectionString.html) 对象。要建立 TLS 连接，必须使用 `MongoClientSettings` 对象绑定 `connectionstring` 和 `sslcontext`。
+ 使用 [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/MongoClients.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/MongoClients.html) 获取 [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/MongoClient.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/MongoClient.html) 对象。

```
public static MongoClient makeDbConnection(String dbName, String DbUserName, String DbPassword,
    String DbClusterEndPoint, String keyStorePass) throws Exception {
    MongoClient connectedClient;
    String connectionOptions = "?replicaSet=rs0&readPreference=secondaryPreferred&retryWrites=false";
    String connectionUrl = "mongodb://" + DbUserName + ":" + DbPassword + "@" + DbClusterEndPoint + ":27017/" +
        dbName + connectionOptions;

    try {
        KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
        try (FileInputStream fis = new FileInputStream("src/main/resources/certs/truststore.jks")) {
            trustStore.load(fis, keyStorePass.toCharArray());
            TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
            tmf.init(trustStore);

            SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
            sslContext.init(null, tmf.getTrustManagers(), new SecureRandom());
            ConnectionString connectionString = new ConnectionString(connectionUrl);
            MongoClientSettings settings = MongoClientSettings.builder()
                .applyConnectionString(connectionString)
                .applyToSslSettings(builder - > {
                    builder.enabled(true);
                    builder.context(sslContext);
                })
                .build();
            connectedClient = MongoClients.create(settings);
        }
        return connectedClient;
    } catch (MongoException e5) {
        throw new RuntimeException(e5);
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}
```

## 步骤 4：处理连接异常
处理连接异常

在 Java 应用程序中使用 DocumentDB 时，处理连接异常对于保持稳健可靠的数据库操作而言至关重要。如果管理得当，这些异常不仅有助于快速诊断问题，还可以确保您的应用程序能够妥善处理临时网络中断或服务器不可用情况，从而提升稳定性和用户体验。与建立连接相关的一些关键异常包括：
+ **`MongoException`** – 一般异常，可能在各种未被更具体异常涵盖的场景中触发。请确保在处理所有其他具体异常后再处理此异常，因为这是一般的捕获所有 MongoDB 异常。
+ **`MongoTimeoutException`** – 在操作超时后触发。例如，查询不存在的集群端点。
+ **`MongoSocketException`** – 针对网络相关问题触发。例如，操作期间网络突然断开连接。
+ **`MongoSecurityException`** – 在身份验证失败时触发。例如，使用不正确的凭证进行连接。
+ **`MongoConfigurationException`** - 当客户端配置中存在错误时触发。例如，使用无效的连接字符串。

## 步骤 5：运行代码
运行代码

以下代码示例将创建 Amazon DocumentDB 连接并打印所有数据库：

```
public static void TestConnection() {
    try (MongoClient mongoClient = makeDbConnection(DATABASE_NAME, DB_USER_NAME, DB_PASSWORD, DB_CLUSTER_ENDPOINT, KEYSTORE_PASSWORD)) {
        List < String > databases = mongoClient.listDatabaseNames().into(new ArrayList < > ());
        System.out.println("Databases: " + databases);
    } catch (MongoException e) {
        System.err.println("MongoDB error: " + e.getMessage());
        throw new RuntimeException(e);
    }
}
```

## 连接最佳实践
最佳实践

以下是使用 MongoDB Java 驱动程序连接到 Amazon DocumentDB 时应考虑的最佳实践：
+ 当您不再需要客户端释放资源时，请务必关闭您的 [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/MongoClient.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/MongoClient.html)。
+ 适当地处理异常并实现正确的错误日志记录。
+ 使用环境变量或 Amazon Secrets Manager 存储敏感信息，例如用户名和密码。

# 使用 Java 在 Amazon DocumentDB 中执行 CRUD 操作
使用 Java 执行 CRUD 操作

本节讨论如何使用 MongoDB Java 驱动程序在 Amazon DocumentDB 中执行 CRUD（创建、读取、更新、删除）操作。

**Topics**
+ [

## 在 DocumentDB 集合中创建和插入文档
](#creating-inserting)
+ [

## 从 DocumentDB 集合中读取和检索数据
](#reading-retrieving)
+ [

## 更新 DocumentDB 集合中的现有文档
](#updating-documents)
+ [

## 从 DocumentDB 集合中移除文档
](#deleting-documents)
+ [

## 使用重试逻辑进行错误处理
](#error-handling)

## 在 DocumentDB 集合中创建和插入文档
在集合中创建和插入文档

向 Amazon DocumentDB 插入文档可以让您向集合添加新数据。根据您的需求和正在处理的数据量，可采用多种方法执行插入。向集合插入单个文档的最基本方法是 `insertOne()`。要一次插入多个文档，可以使用 `insertMany()` 方法，该方法允许您在单个操作中添加文档数组。在 DocumentDB 集合中插入许多文档的另一个方法是 `bulkWrite()`。在本指南中，我们将讨论在 DocumentDB 集合中创建文档的所有方法。

**`insertOne()`**

让我们首先研究如何向 Amazon DocumentDB 集合插入单个文档。可使用 `insertOne()` 方法实现插入单个文档。此方法采用[BsonDocument](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/bson/org/bson/BsonDocument.html)用于插入，并返回一个[https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/result/InsertOneResult.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/result/InsertOneResult.html)可用于获取新插入文档的对象 ID 的对象 ID 的对象。以下示例代码显示了向集合插入一个餐厅文档：

```
Document article = new Document()
    .append("restaurantId", "REST-21G145")
    .append("name", "Future-proofed Intelligent Bronze Hat")
    .append("cuisine", "International")
    .append("rating", new Document()
        .append("average", 1.8)
        .append("totalReviews", 267))
    .append("features", Arrays.asList("Outdoor Seating", "Live Music"));

try {
    InsertOneResult result = collection.insertOne(article);
    System.out.println("Inserted document with the following id: " + result.getInsertedId());
} catch (MongoWriteException e) {
    // Handle duplicate key or other write errors
    System.err.println("Failed to insert document: " + e.getMessage());
    throw e;
} catch (MongoException e) {
    // Handle other MongoDB errors
    System.err.println("MongoDB error: " + e.getMessage());
    throw e;
}
```

使用 `insertOne()` 时，请确保包含适当的错误处理。例如，在上面的代码中，“`restaurantId`”具有唯一索引，因此再次运行此代码将触发以下 `MongoWriteException`：

```
Failed to insert document: Write operation error on server docdbCluster.docdb.amazonaws.com:27017. 
Write error: WriteError{code=11000, message='E11000 duplicate key error collection: Restaurants index: restaurantId_1', details={}}.
```

**insertMany()**

用于向集合插入许多文档的主要方法是 insertMany() 和 `bulkWrite()`。

`insertMany()` 方法是在单个操作中插入多个文档的最简单方法。该方法接受文档列表并将其插入到集合中。当您要插入一批相互独立且无需任何特殊处理或混合操作的新文档时，此方法最为理想。以下代码显示了从文件中读取 JSON 文档并将其插入到集合中的过程。该`insertMany()`函数返回一个可用于获取所有插入文档 IDs 的[https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/result/InsertManyResult.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/result/InsertManyResult.html)`InsertManyResult`对象。

```
// Read JSON file content
String content = new String(Files.readAllBytes(Paths.get(jsonFileName)));
JSONArray jsonArray = new JSONArray(content);

// Convert JSON articles to Documents
List < Document > restaurants = new ArrayList < > ();
for (int i = 0; i < jsonArray.length(); i++) {
    JSONObject jsonObject = jsonArray.getJSONObject(i);
    Document doc = Document.parse(jsonObject.toString());
    restaurants.add(doc);
}
//insert documents in collection
InsertManyResult result = collection.insertMany(restaurants);

System.out.println("Count of inserted documents: " + result.getInsertedIds().size());
```

**[https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/bulk/package-summary.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/bulk/package-summary.html)**

`bulkWrite()` 方法允许在单个批处理中执行多个写入操作（插入、更新、删除）。当您需要在单个批处理中执行不同类型的操作时（例如在更新其他文档的同时插入一些文档），可以使用 `bulkWrite()`。`bulkWrite()` 支持两种类型的批处理写入，有序和无序：
+ *有序操作* –（默认）Amazon DocumentDB 按顺序处理写入操作，并在遇到首个错误时停止。当操作顺序至关重要时（例如后续操作依赖先前操作），有序操作很有用。但是，有序操作通常比无序操作慢。对于有序操作，您必须解决批处理在遇到首个错误时停止的情况，这可能会导致部分操作未被处理。
+ *无序操作* – 允许 Amazon DocumentDB 将插入操作作为数据库中的单次执行来处理。如果一个文档出现错误，将继续对剩余文档执行操作。这在您要插入大量数据并且可以容忍某些失败时尤为有用，例如，在数据迁移或批量导入期间，某些文档可能会因为键重复而导致失败。对于无序操作，您必须处理部分成功场景，即部分操作成功而其他操作失败。

使用 `bulkWrite()` 方法时，需要一些基本类。首先，[https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/WriteModel.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/WriteModel.html) 类充当所有写入操作的基类，并且具有特定的实现如 [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/InsertOneModel.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/InsertOneModel.html)、[https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/UpdateOneModel.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/UpdateOneModel.html)、[https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/UpdateManyModel.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/UpdateManyModel.html)、[https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/DeleteOneModel.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/DeleteOneModel.html) 和 [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/DeleteManyModel.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/DeleteManyModel.html)，可用于处理不同类型的操作。

该[https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/BulkWriteOptions.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/BulkWriteOptions.html)类是配置批量操作行为所必需的，例如设置 ordered/unordered 执行或绕过文档验证。[https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/bulk/BulkWriteResult.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/bulk/BulkWriteResult.html) 类提供有关执行结果的详细信息，包括已插入、已更新和已删除的文档的数量。

对于错误处理，[https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/MongoBulkWriteException.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/MongoBulkWriteException.html) 类至关重要，因为其包含有关批量操作期间所有失败的信息，而 [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/bulk/BulkWriteError.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/bulk/BulkWriteError.html) 类则提供有关单个操作失败的具体详细信息。以下代码显示了在执行单个 `bulkWrite()` 方法调用的过程中插入文档列表以及更新和删除单个文档的示例。该代码还显示了如何使用 [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/BulkWriteOptions.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/BulkWriteOptions.html) 和 [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/bulk/BulkWriteResult.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/bulk/BulkWriteResult.html)，以及如何对 `bulkWrite()` 操作进行正确的错误处理。

```
List < WriteModel < Document >> bulkOperations = new ArrayList < > ();
// get list of 10 documents representing 10 restaurants
List < Document > restaurantsToInsert = getSampleData();

for (Document doc: restaurantsToInsert) {
    bulkOperations.add(new InsertOneModel < > (doc));
}
// Update operation
bulkOperations.add(new UpdateOneModel < > (
    new Document("restaurantId", "REST-Y2E9H5"),
    new Document("", new Document("stats.likes", 20))
    .append("", new Document("rating.average", 4.5))));
// Delete operation
bulkOperations.add(new DeleteOneModel < > (new Document("restaurantId", "REST-D2L431")));

// Perform bulkWrite operation
try {
    BulkWriteOptions options = new BulkWriteOptions()
        .ordered(false); // Allow unordered inserts

    BulkWriteResult result = collection.bulkWrite(bulkOperations, options);

    System.out.println("Inserted: " + result.getInsertedCount());
    System.out.println("Updated: " + result.getModifiedCount());
    System.out.println("Deleted: " + result.getDeletedCount());
} catch (MongoBulkWriteException e) {
    System.err.println("Bulk write error occurred: " + e.getMessage());
    // Log individual write errors
    for (BulkWriteError error: e.getWriteErrors()) {
        System.err.printf("Error at index %d: %s (Code: %d)%n", error.getIndex(), error.getMessage(),
            error.getCode());

        // Log the problematic document
        Document errorDoc = new Document(error.getDetails());
        if (errorDoc != null) {
            System.err.println("Problematic document: " + errorDoc);
        }
    }
} catch (Exception e) {
    System.err.println("Error during bulkWrite: " + e.getMessage());
}
```

**可重试写入**

与 MongoDB 不同，Amazon DocumentDB 不支持可重试写入。因此，您必须在其应用程序中实现自定义的重试逻辑，尤其用于处理网络问题或临时服务不可用情况。通常，实现良好的重试策略包括增加重试之间的延迟和限制总重试次数。有关使用错误处理构建重试逻辑的代码示例，请参阅下面的 [使用重试逻辑进行错误处理](#error-handling)。

## 从 DocumentDB 集合中读取和检索数据
从集合中读取和检索数据

在 Amazon DocumentDB 中查询文档围绕几个关键组件展开，这些组件允许您精确检索和操作数据。该[https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/MongoCollection.html#find()](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/MongoCollection.html#find())方法是 MongoDB Jav APIs a 驱动程序中的基本查询方法。该方法允许复杂数据检索，并提供多种选项用于筛选、排序和投影结果。除了 `find()` 方法之外，[https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/Filters.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/Filters.html) 和 [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/FindIterable.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/FindIterable.html) 是另外两个基本组件，为 MongoDB Java 驱动程序中的查询操作提供构建块。

`Filters` 类是 MongoDB Java 驱动程序中的一个实用程序类，提供流畅的 API 用于构造查询筛选条件。该类提供静态工厂方法，用于创建表示各种查询条件的 `Bson` 对象的实例。最常用的方法包括用于等值比较的 `eq()`，用于数值比较的 `gt()`、`lt()`、`gte()` 和 `lte()`，用于组合多个条件的 `and()` 和 `or()`，用于数组成员资格测试的 `in()` 和 `nin()`，以及用于用于模式匹配的 `regex()`。该类采用类型安全设计，与基于原始文档的查询相比，可提供更好的编译时检查，使其成为在 Java 应用程序中构造 DocumentDB 查询的首选方法。错误处理非常强大，对于无效的筛选条件构造，会抛出明确的异常。

`FindIterable` 是一个专用接口，旨在处理 `find()` 方法的结果。该接口提供一组丰富的方法来优化和控制查询执行，提供流畅的 API 以进行方法链接。该接口包含基本的查询修改方法，例如，`limit()` 用于限制返回的文档数量，`skip()` 用于分页，`sort()` 用于对结果排序，`projection()` 用于选择特定字段，以及 `hint()` 用于选择索引。`FindIterable` 中的 batch、skip 和 limit 操作是基本的分页和数据管理工具，可帮助控制如何从数据库中检索和处理文档。

Batching（`batchSize`）控制单次网络往返中 DocumentDB 向客户端返回的文档数量。设置批处理大小时，DocumentDB 不会一次性返回所有匹配的文档，而是按指定的批处理大小分组返回。

Skip 允许您偏移结果的起始点，本质上是告知 DocumentDB 在开始返回匹配项之前跳过指定数量的文档。例如，`skip(20)` 将绕过前 20 个匹配文档。这通常用于要检索后续结果页面的分页场景。

Limit 限制可以从查询返回的文档总数。当您指定 `limit(n)` 时，即使数据库中存在更多匹配项，DocumentDB 也将在返回“n”个文档后停止返回文档。

从 Amazon DocumentDB 检索文档时，`FindIterable` 支持迭代器和光标模式。使用 `FindIterable` 作为迭代器的优势在于，允许文档延迟加载，并且仅在应用程序请求时才获取文档。使用迭代器的另一个优势在于，您无需负责维护到集群的连接，因此无需显式关闭连接。

`FindIterable` 还支持 [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/MongoCursor.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/MongoCursor.html)，允许在处理 Amazon DocumentDB 查询时使用光标模式。`MongoCursor` 是特定于 MongoDB Java 驱动程序的实现，用于控制数据库操作和资源管理。它实现了`AutoCloseable`接口，允许通过 try-with-resources块进行明确的资源管理，这对于正确关闭数据库连接和释放服务器资源至关重要。默认情况下，光标会在 10 分钟后超时，并且 DocumentDB 不允许您选择更改此超时行为。使用批处理数据时，请确保在光标超时前检索下一批数据。使用 `MongoCursor` 时的一个关键考虑因素是，需要显式关闭以防止资源泄漏。

本节介绍了 `find()`、`Filters` 和 `FindIterable` 的几个示例。

以下代码示例显示了如何使用 `find()` 通过其“restaurantId”字段检索单个文档：

```
Document filter = new Document("restaurantId", "REST-21G145");
Document result = collection.find(filter).first();
```

尽管使用 `Filters` 可以更好地检查编译时错误，但 Java 驱动程序也允许您直接在 `find()` 方法中指定 `Bson` 筛选条件。以下示例代码可将 `Bson` 文档传递给 `find()`：

```
result = collection.find(new Document("$and", Arrays.asList(
    new Document("rating.totalReviews", new Document("$gt", 1000)),
    new Document("priceRange", "$$"))))
```

下一个示例代码显示了将 `Filters` 类与 `find()` 结合使用的几个示例：

```
FindIterable < Document > results;

// Exact match
results = collection.find(Filters.eq("name", "Thai Curry Palace"));

// Not equal
results = collection.find(Filters.ne("cuisine", "Thai"));

// find an element in an array
results = collection.find(Filters.in("features", Arrays.asList("Private Dining")));

// Greater than
results = collection.find(Filters.gt("rating.average", 3.5));

// Between (inclusive)
results = collection.find(Filters.and(
    Filters.gte("rating.totalReviews", 100),
    Filters.lte("rating.totalReviews", 200)));
// AND
results = collection.find(Filters.and(
    Filters.eq("cuisine", "Thai"),
    Filters.gt("rating.average", 4.5)));

// OR
results = collection.find(Filters.or(
    Filters.eq("cuisine", "Thai"),
    Filters.eq("cuisine", "American")));


// All document where the Field exists
results = collection.find(Filters.exists("michelin"));

// Regex
results = collection.find(Filters.regex("name", Pattern.compile("Curry", Pattern.CASE_INSENSITIVE)));

// Find all document where the array contain the list of value regardless of its order
results = collection.find(Filters.all("features", Arrays.asList("Private Dining", "Parking")));

// Array size
results = collection.find(Filters.size("features", 4));
```

以下示例显示了如何在 `FindIterable` 对象上链接 `sort()`、`skip()`、`limit()`、和 `batchSize()` 操作。这些操作的提供顺序将影响查询性能。作为最佳实践，这些操作的顺序应为 `sort()`、`projection()`、`skip()`、`limit()` 和 `batchSize()`。

```
FindIterable < Document > results = collection.find(Filters.gt("rating.totalReviews", 1000))
    // Sorting
    .sort(Sorts.orderBy(
        Sorts.descending("address.city"),
        Sorts.ascending("cuisine")))
    // Field selection
    .projection(Projections.fields(
        Projections.include("name", "cuisine", "priceRange"),
        Projections.excludeId()))

    // Pagination
    .skip(20)
    .limit(10)
    .batchSize(2);
```

以下示例代码显示了在 `FindIterable` 上创建迭代器。该代码使用 Java 的 `forEach` 构造遍历结果集。

```
collection.find(Filters.eq("cuisine", "American")).forEach(doc -> System.out.println(doc.toJson()));
```

在最后一个 `find()` 代码示例中，显示了如何使用 `cursor()` 进行文档检索。其在 try 块中创建光标，确保在代码退出 try 块时关闭光标。

```
try (MongoCursor < Document > cursor = collection.find(Filters.eq("cuisine", "American"))
    .batchSize(25)
    .cursor()) {
    while (cursor.hasNext()) {
        Document doc = cursor.next();
        System.out.println(doc.toJson());
    }
} // Cursor automatically closed
```

## 更新 DocumentDB 集合中的现有文档
更新集合中的现有文档

Amazon DocumentDB 提供了灵活而强大的机制，可修改现有文档，并在文档不存在时插入新文档。MongoDB Java 驱动程序提供了多种更新方法：`updateOne()` 用于单个文档更新，`updateMany()` 用于多个文档更新，以及 `replaceOne()` 用于完整文档替换。除了这三个方法之外，[https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/Updates.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/Updates.html)、[https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/UpdateOptions.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/UpdateOptions.html) 和 [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/result/UpdateResult.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/result/UpdateResult.html) 是其他基本组件，为 MongoDB Java 驱动程序中的更新操作提供构建块。

MongoDB Java 驱动程序中的 `Updates` 类是一个实用程序类，提供用于创建更新运算符的静态工厂方法。其充当主要生成器，以类型安全且可读的方式构造更新操作。诸如 `set()`、`unset()`、和 `inc()` 之类的基本方法允许直接修改文档。当使用 `Updates.combine()` 方法（允许以原子方式执行多个更新操作，从而确保数据一致性）组合多个操作时，该类的功能优势尤为突出。

`UpdateOptions` 是 MongoDB 的 Java 驱动程序中的一个功能强大的配置类，为文档更新操作提供基本的自定义功能。该类的两个重要方面：为更新操作提供更新插入和数组筛选条件支持。通过 `upsert(true)` 启用的更新插入功能允许在更新操作期间未找到匹配文档时创建新文档。通过 `arrayFilters()`，更新操作可以精确更新满足特定条件的数组元素。

MongoDB 的 Java 驱动程序中的 `UpdateResult` 提供反馈机制，详细说明更新操作的结果。该类封装了三个关键指标：与更新条件匹配的文档数量（`matchedCount`）、实际修改的文档数量（`modifiedCount`）以及有关任何已更新插入文档的信息（`upsertedId`）。了解这些指标对于正确处理错误、验证更新操作和维护应用程序中的数据一致性而言至关重要。

### 更新并替换单个文档
更新单个文档

在 DocumentDB 中，可以使用 updateOne() 方法来实现更新单个文档。该方法采用三个参数：通常由 `Filters` 类提供的用于标识要更新文档的 filter 参数、用于确定要更新字段的 `Updat`e 参数以及用于设置不同更新选项的可选 `UpdateOptions` 参数。使用 `updateOne()` 方法只会更新第一个符合选择条件的文档。以下示例代码将更新一个文档的单个字段：

```
collection.updateOne(Filters.eq("restaurantId", "REST-Y2E9H5"),
    Updates.set("name", "Amazing Japanese sushi"));
```

要更新一个文档中的多个字段，请结合使用 `updateOne()` 和 `Update.combine()`，如以下示例所示。此示例还显示了如何向文档中的数组添加项。

```
List<Bson> updates = new ArrayList<>();
// Basic field updates
updates.add(Updates.set("name", "Shanghai Best"));
// Array operations
updates.add(Updates.addEachToSet("features", Arrays.asList("Live Music")));
// Counter updates
updates.add(Updates.inc("rating.totalReviews", 10));
// Combine all updates
Bson combinedUpdates = Updates.combine(updates);
// Execute automic update with one call
collection.updateOne(Filters.eq("restaurantId","REST-1J83NH"), combinedUpdates);
```

以下代码示例演示了如何更新数据库中的文档。如果指定的文档不存在，则操作会自动将其作为新文档插入。此代码还显示了如何使用通过 `UpdateResult` 对象提供的指标。

```
Bson filter = Filters.eq("restaurantId", "REST-0Y9GL0");
Bson update = Updates.set("cuisine", "Indian");
// Upsert operation
UpdateOptions options = new UpdateOptions().upsert(true);
UpdateResult result = collection.updateOne(filter, update, options);

if (result.getUpsertedId() != null) {
   	System.out.println("Inserted document with _id: " + result.getUpsertedId());
} else {
    	System.out.println("Updated " + result.getModifiedCount() + " document(s)");
}
```

以下代码示例演示了如何使用 `replaceOne()` 方法将现有文档完全替换为新文档，而非更新单个字段。`replaceOne()` 方法将覆盖整个文档，仅保留原始文档的 `_id` 字段。如果多个文档符合筛选条件，则仅替换首个匹配文档。

```
Document newDocument = new Document()
                .append("restaurantId", "REST-0Y9GL0")
                .append("name", "Bhiryani Adda")
                .append("cuisine", "Indian")
                .append("rating", new Document()
                        .append("average", 4.8)
                        .append("totalReviews", 267))
                .append("features", Arrays.asList("Outdoor Seating", "Live Music"));

UpdateResult result = collection.replaceOne(
                    Filters.eq("restaurantId", "REST-0Y9GL0"),
                    newDocument);
System.out.printf("Modified %d document%n", result.getModifiedCount());
```

### 更新多个文档
更新多个文档

可通过两种方式同时更新集合中的多个文档。您可以使用 `updateMany()` 方法，或结合使用 [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/UpdateManyModel.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/UpdateManyModel.html) 和 `bulkWrite()` 方法。`updateMany()` 方法使用 filter 参数来选择要更新的文档，使用 `Update` 参数来标识要更新的字段，使用可选的 `UpdateOptions` 参数来指定更新选项。

以下示例代码演示了 `updateMany()` 方法的使用：

```
Bson filter = Filters.and(
    Filters.in("features", Arrays.asList("Private Dining")),
    Filters.eq("cuisine", "Thai"));
UpdateResult result1 = collection.updateMany(filter, Updates.set("priceRange", "$$$"));
```

以下示例代码演示了使用相同更新的 `bulkWrite()` 方法：

```
BulkWriteOptions options = new BulkWriteOptions().ordered(false);
List < WriteModel < Document >> updates = new ArrayList < > ();
Bson filter = Filters.and(
    Filters.in("features", Arrays.asList("Private Dining")),
    Filters.eq("cuisine", "Indian"));
Bson updateField = Updates.set("priceRange", "$$$");
updates.add(new UpdateManyModel < > (filter, updateField));
BulkWriteResult result = collection.bulkWrite(updates, options);
System.out.printf("Modified %d document%n", result.getModifiedCount());
```

## 从 DocumentDB 集合中移除文档
从集合中移除文档

MongoDB Java 驱动程序提供 `deleteOne()` 用于移除单个文档，并且提供 `deleteMany()` 用于移除符合特定条件的多个文档。与更新类似，删除操作也可以与 `bulkWrite()` 方法结合使用。`deleteOne()` 和 `deleteMany()` 都会返回 [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/result/DeleteResult.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/result/DeleteResult.html) 对象，该对象提供有关操作结果的信息，包括已删除文档的数量。以下是使用 `deleteMany()` 移除多个文档的示例：

```
Bson filter = Filters.and(
    Filters.eq("cuisine", "Thai"),
    Filters.lt("rating.totalReviews", 50));
DeleteResult result = collection.deleteMany(filter);
System.out.printf("Deleted %d document%n", result.getDeletedCount());
```

## 使用重试逻辑进行错误处理
错误处理

Amazon DocumentDB 强大的错误处理策略应将错误分为可重试错误（例如网络超时、连接问题）和不可重试错误（例如身份验证失败、无效查询）。对于因可重试的错误而导致的操作失败，可以实现每次重试时间延迟以及最大重试次数。CRUD 操作应该位于捕获 [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/MongoException.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/MongoException.html) 及其子类的 try-catch 块中。此外，还应包括错误的监控与日志记录，以实现操作可见性。以下是显示如何实现重试错误处理的示例代码：

```
int MAX_RETRIES = 3;
int INITIAL_DELAY_MS = 1000;
int retryCount = 0;

while (true) {
    try {
        crud_operation(); //perform crud that will throw MongoException or one of its subclass
        break;
    } catch (MongoException e) {
        if (retryCount < MAX_RETRIES) {
            retryCount++;
            long delayMs = INITIAL_DELAY_MS * (long) Math.pow(2, retryCount - 1);
            try {
                TimeUnit.MILLISECONDS.sleep(delayMs);
            } catch (InterruptedException t) {
                Thread.currentThread().interrupt();
                throw new RuntimeException("Retry interrupted", t);
            }
            continue;
        } else
            throw new RuntimeException("Crud operation failed", e);
    }
}
```

# 使用 Java 在 Amazon DocumentDB 中进行索引管理
使用 Java 进行索引管理

索引允许从 Amazon DocumentDB 集合中高效检索数据。如果没有索引，DocumentDB 必须扫描集合中的每个文档才能返回满足给定查询的结果。本主题提供有关如何使用 MongoDB Java 驱动程序创建、删除和列出索引的信息。还讨论了如何确定查询中是否使用了特定索引，以及如何向 Amazon DocumentDB 提供提示以使用特定索引。

**Topics**
+ [创建索引](#creating-indexes)
+ [删除索引](#dropping-indes)
+ [确定索引选择并提供索引提示](#w2aac43b9b7c17c13)

Amazon DocumentDB 支持多种类型的索引。有关所有受支持索引的全面概述，请参阅此[博客文章](https://www.amazonaws.cn/blogs/database/how-to-index-on-amazon-documentdb-with-mongodb-compatibility/)。

## 使用 Java 创建索引
创建索引

可通过两种机制使用 MongoDB Java 驱动程序在 Amazon DocumentDB 中创建索引：通过 `runCommand()`，以及通过 `createIndex()` 方法（创建单个索引）或 `createIndexes()` 方法（创建多个索引）。使用 `createIndex()` 和 `createIndexes()` 方法的原因之一是，通过捕获与创建索引相关的特定错误，您可以构建更好的错误处理。相较于 `runCommand()`，更推荐使用这些方法的另一个原因是，MongoDB Java 驱动程序为索引创建和操作提供了一组丰富的支持类。请注意，只有当您使用 `createIndex()` 或 `createIndexes()` 方法时才可以使用这些支持类。有三个支持类：
+ **[https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/Indexes.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/Indexes.html)** – 该类充当实用程序类，提供用于创建各种类型索引的静态工厂方法。该类简化了创建复杂索引定义的过程，通常和其他与索引相关的类一起使用。
+ **[https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/IndexModel.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/IndexModel.html)** – 这是一个基本类，同时封装了索引键定义及其选项。其代表了一个完整的索引规范，将要索引的内容（键）与索引方式（选项）相结合。该类在同时创建多个索引时尤为有用，因为允许您定义可以传递给 `createIndexes()` 方法的索引规范集合。
+ **[https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/IndexOptions.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/IndexOptions.html)** – 这是一个全面的配置类，为自定义索引行为提供了一组丰富的方法。包含唯一索引、稀疏索引、过期时间（TTL）和部分筛选条件表达式的设置。通过方法链接，您可以配置后台索引构建和唯一约束等多个选项。

**创建单个索引**

此示例显示了如何在后台使用 `createIndex(`) 方法创建单个索引。要了解后台和前台索引创建，请参阅 [索引构建类型](managing-indexes.md#index-build-types)。以下代码示例使用 [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/IndexOptions.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/IndexOptions.html) 在后台创建名为“unique\$1restaurantId\$1idx”的唯一索引。然后将此 `IndexOptions` 对象传递给 `createIndex()` 方法。

```
collection.createIndex(
    Indexes.ascending("restaurantId"),
    new IndexOptions()
        .unique(true)
        .name("unique_restaurantId_idx")
        .background(true));
```

**创建多个索引**

此示例使用 `createIndexes()` 方法创建多个索引。首先使用 [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/IndexModel.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/IndexModel.html) 对象为每个索引构建选项，然后将 `IndexModel` 对象列表传递给 `createIndexes()` 方法。以下代码示例显示了如何使用 [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/Indexes.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/Indexes.html) 实用程序类创建复合索引。该类还用于指定要使用升序还是降序排序顺序创建索引。在创建多个索引后，该类会通过调用 `listIndexes()` 方法来验证索引创建情况。

```
// Single Field Index on cuisine
IndexModel singleIndex = new IndexModel(
    Indexes.ascending("cuisine"),
    new IndexOptions().name("cuisine_idx"));

// Compound Index
IndexModel compoundIndex = new IndexModel(
    Indexes.compoundIndex(
        Indexes.ascending("address.state"),
        Indexes.ascending("priceRange")),
    new IndexOptions().name("location_price_idx"));

// Build a list of IndexModel for the indexes
List < IndexModel > indexes = Arrays.asList(
    singleIndex,
    compoundIndex
);

collection.createIndexes(indexes);

// Verify created indexes
collection.listIndexes().forEach(index - > System.out.println("Created index: " + index.toJson()));
```

**创建稀疏索引和部分索引**

此示例显示了通过为每种类型的索引创建 [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/IndexModel.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/IndexModel.html)，来创建稀疏索引和部分索引。

```
// Sparse Index Model, this will identify only those documents that have a
// michelin star rating
IndexModel sparseIndex = new IndexModel(
    Indexes.ascending("michelin.star"),
    new IndexOptions()
    .name("michelin_sparse_idx")
    .sparse(true));

// Partial Index Model where the restaurant is active and has a rating of 4 and above
IndexModel partialIndex = new IndexModel(
    Indexes.ascending("rating.average"),
    new IndexOptions()
    .name("high_rated_active_idx")
    .partialFilterExpression(
        Filters.and(
            Filters.eq("isActive", true),
            Filters.gte("rating.average", 4.0))));
```

**创建文本索引**

此示例显示了如何创建文本索引。一个集合上只允许有一个文本索引，但该文本索引可以是覆盖多个字段的复合索引。在文本索引中使用多个字段时，也可以为索引中的每个字段分配权重。Amazon DocumentDB 不支持数组字段上的文本索引，尽管复合文本索引中最多可以使用 30 个字段，但只能为三个字段分配权重。

```
IndexModel textIndex = new IndexModel(
    new Document()
    .append("name", "text")
    .append("description", "text")
    .append("cuisine", "text"),
    new IndexOptions()
    .name("restaurant_text_idx")
    .weights(new Document()
        .append("name", 10) // Restaurant name gets highest weight
        .append("description", 5) // Description get medium weight
        .append("cuisine", 2) // Cuisine type gets low weight
    ));

collection.createIndex(textIndex.getKeys(), textIndex.getOptions());
```

**使用 `runCommand()` 创建索引**

Amazon DocumentDB 支持并行索引创建，以缩短创建索引所需要的时间。并行索引使用多个并发工作程序。用于创建索引的默认工作程序有两个。此篇[博客文章](https://www.amazonaws.cn/blogs/database/unlock-the-power-of-parallel-indexing-in-amazon-documentdb/)对并行索引进行了深入讨论。当前，MongDB Java 驱动程序不支持您在使用 `createIndex()` 或 `createIndexes()` 时指定 worker 选项，因此指定工作程序的唯一方法是通过 `runCommand`。以下代码示例演示了如何使用 `runCommand` 创建可将工作程序增加到四个的索引：

```
Document command = new Document("createIndexes", "Restaurants")
    .append("indexes", Arrays.asList(
        new Document("key", new Document("name", 1))
        .append("name", "restaurant_name_idx")
        .append("workers", 4) // Specify number of workers
    ));

Document commendResult = connectedDB.runCommand(command);
```

## 删除索引
删除索引

MongoDB Java 驱动程序提供了多个可删除索引的方法，可满足不同的场景和您的首选项。您可以按名称、按键规范删除索引，也可以一次性删除所有索引。可以在集合对象上调用 `dropIndex()` 和 `dropIndexes()` 方法来删除索引。按名称删除索引时，应确保使用正确的索引名称，索引名称可能并不总是直观的，特别是对于复合索引或自动生成的索引而言。尝试删除不存在的索引将导致 [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/MongoCommandException.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/MongoCommandException.html)。不能删除 `default _id` 索引，因为该索引可以确保集合中文档的唯一性。

以下代码示例演示了如何通过提供创建索引的字段名称或通过删除所有索引来删除索引：

```
String indexName = "unique_restaurantId_idx";
Document keys = new Document("cuisine", 1);
// Drop index by name
collection.dropIndex(indexName);
            
// Drop index by keys
collection.dropIndex(keys);
            
// Drop all indexes
collection.dropIndexes();
```

使用多个键删除索引时，请确保存在一个包含所有指定键的复合索引，并且键的顺序正确。上面的索引创建示例代码显示了“cuisine”和 features 的复合键。如果您尝试删除该复合键，但顺序不是创建时使用的顺序，则会出现如下 MongoCommnadException 错误：

```
Document keys = new Document("features", 1)
    .append("cuisine", 1);
try {
    // Drop index by keys
    collection.dropIndex(keys);
    System.out.println("Successfully dropped index with keys: " + keys.toJson());

} catch (MongoCommandException commErr) {
    System.out.println("Error dropping index: " + commErr.getErrorMessage());
    throw new RuntimeException("MongoCommandException was thrown while dropping index", commErr);
}
```

将显示以下错误：

```
Error dropping index: Cannot drop index: index not found.
Tests run: 3, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 0.819 sec <<< FAILURE!
com.amazon.docdb.guide.DocDBGuideTest.testindexGuide()  Time elapsed: 0.817 sec  <<< FAILURE!
org.opentest4j.AssertionFailedError: Unexpected exception thrown: java.lang.RuntimeException: MongoCommandException was thrown while dropping index
```

## 确定索引选择并提供索引提示
确定索引选择并提供索引提示

使用 Amazon DocumentDB 中的解释功能对于您了解查询性能和索引使用情况而言至关重要。执行查询时，您可以附加 `explain()` 方法以获取有关查询计划的详细信息，包括正在使用的索引（如果有）。`explain()` 输出提供了对查询执行阶段、检查的文档数量以及每个阶段所花费时间的洞察。这些信息对于确定特定索引是否得到有效使用或查询是否可以从不同的索引结构中获益而言非常有用。

`explain()` 方法可以与 `find()` 方法链接。`explain()` 方法可以采用可选的 [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/ExplainVerbosity.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/ExplainVerbosity.html) 枚举来确定 `explain()` 返回的详细程度级别。目前，DocumentDB 仅支持 `EXECUTION_STATS` 和 `QUERY_PLANNER` 枚举器。以下代码示例显示了如何获取特定查询的查询计划程序：

```
// Query we want to analyze
Document query = new Document()
    .append("cuisine", "Thai")
    .append("rating.average", new Document("$gte", 4.0));


Document allPlansExplain = collection.find(query).explain(ExplainVerbosity.QUERY_PLANNER);
System.out.println("All Plans Explain:\n" + allPlansExplain.toJson());
```

针对查询计划程序详细程度级别返回以下 JSON 文档：

```
{
  "queryPlanner": {
    "plannerVersion": 1,
    "namespace": "ProgGuideData.Restaurants",
    "winningPlan": {
      "stage": "IXSCAN",
      "indexName": "cuisine_idx",
      "direction": "forward"
    }
  },
  "serverInfo": {
    "host": "guidecluster3",
    "port": 27017,
    "version": "5.0.0"
  },
  "ok": 1,
  "operationTime": {
    "$timestamp": {
      "t": 1739221668,
      "i": 1
    }
  }
}
```

您可以通过多种方式来控制或强制 Amazon DocumentDB 使用特定索引。`hint()` 和 `hintString()` 方法允许您通过显式指定查询应使用的索引来覆盖查询优化程序的默认索引选择行为。虽然 DocumentDB 的查询优化程序通常能做出合理的索引选择，但在某些场景下，通过 `hint()` 或 `hintString()` 强制使用特定索引可能有益，例如在处理偏斜数据或测试索引性能时。

以下代码示例强制使用复合索引“cuisine\$1features\$1idx”来处理在上述代码中运行的相同查询：

```
// Query we want to analyze
Document query = new Document()
    .append("cuisine", "Thai")
    .append("rating.average", new Document("$gte", 4.0));

List < Document > queryDocs = new ArrayList < > ();
collection.find(query).hintString("cuisine_features_idx").forEach(doc - > queryDocs.add(doc));
```

# 使用 Amazon DocumentDB 和 Java 进行事件驱动型编程
事件驱动型编程

Amazon DocumentDB 环境中的事件驱动型编程代表了一种强大的架构模式，其中数据库变更充当主要事件生成器，用于触发后续业务逻辑和进程。在 DocumentDB 集合中插入、更新或删除记录时，这些变更充当事件，用于自动启动各种下游进程、通知或数据同步任务。该模式在现代分布式系统中尤为重要，在这些系统中，多个应用程序或服务需要对数据变更做出实时反应。在 DocumentDB 中实现事件驱动型编程的主要机制是通过变更流。

**注意**  
本指南假设您已在使用的集合上启用了变更流。要了解如何在集合上启用变更流，请参阅 [将变更流与 Amazon DocumentDB 结合使用](change_streams.md)。

**通过 Java 应用程序处理变更流**

MongoDB 的 Java 驱动程序中的 `watch()` 方法是监控 Amazon DocumentDB 中实时数据变更的主要机制。`watch()` 方法可以通过 [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/MongoClient.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/MongoClient.html)、[https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/MongoDatabase.html#watch(com.mongodb.client.ClientSession,java.lang.Class)](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/MongoDatabase.html#watch(com.mongodb.client.ClientSession,java.lang.Class)) 和 [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/MongoCollection.html#watch()](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/MongoCollection.html#watch()) 对象进行调用。

`watch()` 方法将返回支持各种配置选项的 [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/ChangeStreamIterable.html#startAtOperationTime(org.bson.BsonTimestamp)](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/ChangeStreamIterable.html#startAtOperationTime(org.bson.BsonTimestamp)) 的实例，包括用于更新的完整文档查找、提供恢复令牌和时间戳以确保可靠性，以及用于筛选变更的管道聚合阶段。

[https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/ChangeStreamIterable.html#startAtOperationTime(org.bson.BsonTimestamp)](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/ChangeStreamIterable.html#startAtOperationTime(org.bson.BsonTimestamp)) 将实现核心 Java 接口 `Iterable`，可以与 `forEach()` 结合使用。要使用 `forEach()` 捕获事件，请将回调函数传递给处理变更事件的 `forEach()`。以下代码片段显示了如何在集合上打开变更流以启动变更事件监控：

```
ChangeStreamIterable < Document > iterator = collection.watch();
iterator.forEach(event - > {
    System.out.println("Received a change: " + event);
});
```

遍历所有变更事件的另一种方法是打开光标，该光标保持与集群的连接，并在发生新的变更事件时持续接收这些事件。要获取变更流光标，请使用 [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/ChangeStreamIterable.html#startAtOperationTime(org.bson.BsonTimestamp)](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/ChangeStreamIterable.html#startAtOperationTime(org.bson.BsonTimestamp)) 对象的 `cursor()` 方法。以下代码示例显示了如何使用光标监控变更事件：

```
try (MongoChangeStreamCursor < ChangeStreamDocument < Document >> cursor = collection.watch().cursor()) {
    System.out.println(cursor.tryNext());
}
```

最佳做法是，要么在 try-with-resource语句[https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/MongoChangeStreamCursor.html#getResumeToken()](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/MongoChangeStreamCursor.html#getResumeToken())中创建，要么手动关闭游标。在 [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/ChangeStreamIterable.html#startAtOperationTime(org.bson.BsonTimestamp)](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/ChangeStreamIterable.html#startAtOperationTime(org.bson.BsonTimestamp)) 上调用 `cursor()` 方法将返回通过 [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/changestream/ChangeStreamDocument.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/changestream/ChangeStreamDocument.html) 对象创建的 `MongoChangeStreamCursor`。

[https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/changestream/ChangeStreamDocument.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/changestream/ChangeStreamDocument.html) 类是表示流中各个变更事件的关键组件。包含有关每项修改的详细信息，包括操作类型（插入、更新、删除、替换）、文档键、命名空间信息以及完整的文档内容（如果有）。该类提供了访问变更事件各个方面的方法，例如 `getOperationType()` 用于确定变更类型，`getFullDocument()` 用于访问完整文档状态，以及 `getDocumentKey()` 用于识别被修改的文档。

[https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/changestream/ChangeStreamDocument.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/changestream/ChangeStreamDocument.html) 对象提供两条重要信息，即恢复令牌和变更事件发生时间。

DocumentDB 变更流中的恢复令牌和基于时间的操作为保持连续性和管理历史数据访问提供了关键机制。恢复令牌是为每个变更事件生成的唯一标识符，用作书签，允许应用程序在断开连接或发生故障后从特定点重新启动变更流处理。创建变更流光标时，可以通过 `resumeAfter()` 选项使用先前存储的恢复令牌，使流能够从中断处继续，而非从头开始或丢失事件。

变更流中基于时间的操作提供了不同的方法来管理变更事件监控的起点。`startAtOperationTime()` 选项允许您开始监视在特定时间戳或之后发生的变更。这些基于时间的功能在需要历史数据处理、 point-in-time恢复或系统间同步的场景中特别有价值。

以下代码示例检索与插入文档关联的事件，捕获其恢复令牌，然后提供该令牌以开始监控插入事件之后的事件。该事件与更新事件关联，然后获取更新发生时的集群时间，并使用该时间戳作为进一步处理的起点。

```
BsonDocument resumeToken;
BsonTimestamp resumeTime;

try (MongoChangeStreamCursor < ChangeStreamDocument < Document >> cursor = collection.watch().cursor()) {
    System.out.println("****************** Insert Document *******************");
    ChangeStreamDocument < Document > insertChange = cursor.tryNext();
    resumeToken = insertChange.getResumeToken();
    printJson(cursor.tryNext());
}
try (MongoChangeStreamCursor < ChangeStreamDocument < Document >> cursor = collection.watch()
    .resumeAfter(resumeToken)
    .cursor()) {
    System.out.println("****************** Update Document *******************");
    ChangeStreamDocument < Document > insertChange = cursor.tryNext();
    resumeTime = insertChange.getClusterTime();
    printJson(cursor.tryNext());
}
try (MongoChangeStreamCursor < ChangeStreamDocument < Document >> cursor = collection.watch()
    .startAtOperationTime(resumeTime)
    .cursor()) {
    System.out.println("****************** Delete Document *******************");
    printJson(cursor.tryNext());
  }
```

默认情况下，更新变更事件不包含完整文档，仅包含所做变更。如果您需要访问已更新的完整文档，则可以在 [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/ChangeStreamIterable.html#startAtOperationTime(org.bson.BsonTimestamp)](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-sync/com/mongodb/client/ChangeStreamIterable.html#startAtOperationTime(org.bson.BsonTimestamp)) 对象上调用 `fullDocument()` 方法。请记住，当您请求返回更新事件的完整文档时，会返回调用变更流时存在的文档。

此方法采用 [https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/changestream/FullDocument.html](https://mongodb.github.io/mongo-java-driver/5.3/apidocs/mongodb-driver-core/com/mongodb/client/model/changestream/FullDocument.html) 枚举作为参数。目前，Amazon DocumentDB 仅支持 DEFAULT 和 `UPDATE_LOOKUP` 值。以下代码片段显示了在开始监视变更时如何请求提供更新事件的完整文档：

```
try (MongoChangeStreamCursor < ChangeStreamDocument < Document >> cursor = collection.watch().fullDocument(FullDocument.UPDATE_LOOKUP).cursor())
```