

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

# 从AWS SDK for Java 2.x 调用 AWS 服务
<a name="work-with-services"></a>

本部分提供有关如何使用精选 AWS 服务的简短教程和指导。如需完整的示例集合，请参阅[代码示例部分](java_code_examples.md)。

**Topics**
+ [CloudWatch](examples-cloudwatch.md)
+ [AWS 数据库服务](examples-databases.md)
+ [DynamoDB](examples-dynamodb.md)
+ [Amazon EC2](examples-ec2.md)
+ [IAM](examples-iam.md)
+ [Kinesis](examples-kinesis.md)
+ [Lambda](examples-lambda.md)
+ [Amazon S3](examples-s3.md)
+ [Amazon SNS](examples-simple-notification-service.md)
+ [Amazon SQS](examples-sqs.md)
+ [Amazon Transcribe](examples-transcribe.md)

# 与... 一起工作 CloudWatch
<a name="examples-cloudwatch"></a>

本节提供了使用 适用于 Java 的 AWS SDK 2.x 对 [Amazon CloudWatch](https://docs.aws.amazon.com//AmazonCloudWatch/latest/monitoring/WhatIsCloudWatch.html) 进行编程的示例。

 Amazon CloudWatch 实时监控您的 Amazon Web Services (AWS) 资源和您运行 AWS 的应用程序。您可以使用 CloudWatch 来收集和跟踪指标，这些指标是您可以衡量资源和应用程序的变量。 CloudWatch 警报会根据您定义的规则发送通知或自动更改您正在监控的资源。

以下示例仅包含演示每种方法所需的代码。[完整的示例代码可在上找到 GitHub](https://github.com/awsdocs/aws-doc-sdk-examples/tree/main/javav2)。您可以在那里下载单个源文件，也可以将存储库复制到本地以获得所有示例，然后构建并运行这些示例。

**Topics**
+ [从中获取指标 CloudWatch](examples-cloudwatch-get-metrics.md)
+ [将自定义指标数据发布到 CloudWatch](examples-cloudwatch-publish-custom-metrics.md)
+ [处理 CloudWatch 警报](examples-cloudwatch-create-alarms.md)
+ [使用 Amazon CloudWatch 活动](examples-cloudwatch-send-events.md)

# 从中获取指标 CloudWatch
<a name="examples-cloudwatch-get-metrics"></a>

## 列出指标
<a name="listing-metrics"></a>

要列出 CloudWatch 指标，请创建[ListMetricsRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/cloudwatch/model/ListMetricsRequest.html)并调用 CloudWatchClient's `listMetrics` 方法。您可以使用 `ListMetricsRequest` 通过命名空间、指标名称或维度筛选返回的指标。

**注意**  
 AWS 服务发布的指标和维度列表可在 Amazon CloudWatch 用户指南的[Amazon CloudWatch 指标和维度参考](https://docs.aws.amazon.com//AmazonCloudWatch/latest/monitoring/aws-services-cloudwatch-metrics.html)中找到。

 **导入** 

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.cloudwatch.CloudWatchClient;
import software.amazon.awssdk.services.cloudwatch.model.CloudWatchException;
import software.amazon.awssdk.services.cloudwatch.model.ListMetricsRequest;
import software.amazon.awssdk.services.cloudwatch.model.ListMetricsResponse;
import software.amazon.awssdk.services.cloudwatch.model.Metric;
```

 **代码** 

```
    public static void listMets( CloudWatchClient cw, String namespace) {

        boolean done = false;
        String nextToken = null;

        try {
            while(!done) {

                ListMetricsResponse response;

                if (nextToken == null) {
                   ListMetricsRequest request = ListMetricsRequest.builder()
                        .namespace(namespace)
                        .build();

                 response = cw.listMetrics(request);
                } else {
                  ListMetricsRequest request = ListMetricsRequest.builder()
                        .namespace(namespace)
                        .nextToken(nextToken)
                        .build();

                response = cw.listMetrics(request);
            }

            for (Metric metric : response.metrics()) {
                System.out.printf(
                        "Retrieved metric %s", metric.metricName());
                System.out.println();
            }

            if(response.nextToken() == null) {
                done = true;
            } else {
                nextToken = response.nextToken();
            }
        }

        } catch (CloudWatchException e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
    }
```

[ListMetricsResponse](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/cloudwatch/model/ListMetricsResponse.html)通过调用其`getMetrics`方法返回指标。

结果可以*分页*。要检索下一批结果，请对响应对象调用 `nextToken` 并使用该令牌值构建新的请求对象。然后使用新请求再次调用 `listMetrics` 方法。

请参阅上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/f4eaf2b2971805cfb2b87a8e5ab408f83169432e/javav2/example_code/cloudwatch/src/main/java/com/example/cloudwatch/ListMetrics.java) GitHub。

## 更多信息
<a name="more-information"></a>
+  [ListMetrics](https://docs.aws.amazon.com//AmazonCloudWatch/latest/APIReference/API_ListMetrics.html)在 Amazon CloudWatch API 参考中

# 将自定义指标数据发布到 CloudWatch
<a name="examples-cloudwatch-publish-custom-metrics"></a>

许多 AWS 服务在以 “`AWS`” 开头的命名空间中发布[自己的指标](https://docs.aws.amazon.com//AmazonCloudWatch/latest/monitoring/aws-services-cloudwatch-metrics.html)。您也可以使用自己的命名空间发布自定义指标数据（只要不是以 `AWS` “” 开头即可）。

## 发布自定义指标数据
<a name="cwid1"></a>

要发布您自己的指标数据，请使用调用 CloudWatchClient's `putMetricData` 方法[PutMetricDataRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/cloudwatch/model/PutMetricDataRequest.html)。`PutMetricDataRequest`必须包括用于数据的自定义命名空间，以及有关[MetricDatum](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/cloudwatch/model/MetricDatum.html)对象中数据点本身的信息。

**注意**  
您无法指定以“`AWS`”开头的命名空间。以 “`AWS`” 开头的命名空间保留供产品使用。 Amazon Web Services 

 **导入** 

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.cloudwatch.CloudWatchClient;
import software.amazon.awssdk.services.cloudwatch.model.Dimension;
import software.amazon.awssdk.services.cloudwatch.model.MetricDatum;
import software.amazon.awssdk.services.cloudwatch.model.StandardUnit;
import software.amazon.awssdk.services.cloudwatch.model.PutMetricDataRequest;
import software.amazon.awssdk.services.cloudwatch.model.CloudWatchException;
import java.time.Instant;
import java.time.ZoneOffset;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
```

 **代码** 

```
    public static void putMetData(CloudWatchClient cw, Double dataPoint ) {

        try {
            Dimension dimension = Dimension.builder()
                    .name("UNIQUE_PAGES")
                    .value("URLS")
                    .build();

            // Set an Instant object
            String time = ZonedDateTime.now( ZoneOffset.UTC ).format( DateTimeFormatter.ISO_INSTANT );
            Instant instant = Instant.parse(time);

            MetricDatum datum = MetricDatum.builder()
                .metricName("PAGES_VISITED")
                .unit(StandardUnit.NONE)
                .value(dataPoint)
                .timestamp(instant)
                .dimensions(dimension).build();

            PutMetricDataRequest request = PutMetricDataRequest.builder()
                .namespace("SITE/TRAFFIC")
                .metricData(datum).build();

            cw.putMetricData(request);

        } catch (CloudWatchException e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
        System.out.printf("Successfully put data point %f", dataPoint);
     }
```

请参阅上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/f4eaf2b2971805cfb2b87a8e5ab408f83169432e/javav2/example_code/cloudwatch/src/main/java/com/example/cloudwatch/PutMetricData.java) GitHub。

## 更多信息
<a name="more-information"></a>
+  [使用 Amazon CloudWatch 用户指南中的 Amazon CloudWatch 指标](https://docs.aws.amazon.com//AmazonCloudWatch/latest/monitoring/working_with_metrics.html)。
+ AWS 《 Amazon CloudWatch 用户指南》中的@@ [命名空间](https://docs.aws.amazon.com//AmazonCloudWatch/latest/monitoring/aws-services-cloudwatch-metrics.html)。
+  [PutMetricData](https://docs.aws.amazon.com//AmazonCloudWatch/latest/APIReference/API_PutMetricData.html)在 Amazon CloudWatch API 参考中。

# 处理 CloudWatch 警报
<a name="examples-cloudwatch-create-alarms"></a>

## 创建警报
<a name="create-an-alarm"></a>

要根据 CloudWatch 指标创建警报，请调用[PutMetricAlarmRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/cloudwatch/model/PutMetricAlarmRequest.html)填充警报条件 CloudWatchClient的's `putMetricAlarm` 方法。

 **导入** 

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.cloudwatch.CloudWatchClient;
import software.amazon.awssdk.services.cloudwatch.model.Dimension;
import software.amazon.awssdk.services.cloudwatch.model.PutMetricAlarmRequest;
import software.amazon.awssdk.services.cloudwatch.model.ComparisonOperator;
import software.amazon.awssdk.services.cloudwatch.model.Statistic;
import software.amazon.awssdk.services.cloudwatch.model.StandardUnit;
import software.amazon.awssdk.services.cloudwatch.model.CloudWatchException;
```

 **代码** 

```
    public static void putMetricAlarm(CloudWatchClient cw, String alarmName, String instanceId) {

        try {
            Dimension dimension = Dimension.builder()
                .name("InstanceId")
                .value(instanceId).build();

            PutMetricAlarmRequest request = PutMetricAlarmRequest.builder()
                .alarmName(alarmName)
                .comparisonOperator(
                        ComparisonOperator.GREATER_THAN_THRESHOLD)
                .evaluationPeriods(1)
                .metricName("CPUUtilization")
                .namespace("AWS/EC2")
                .period(60)
                .statistic(Statistic.AVERAGE)
                .threshold(70.0)
                .actionsEnabled(false)
                .alarmDescription(
                        "Alarm when server CPU utilization exceeds 70%")
                .unit(StandardUnit.SECONDS)
                .dimensions(dimension)
                .build();

            cw.putMetricAlarm(request);
            System.out.printf(
                    "Successfully created alarm with name %s", alarmName);

        } catch (CloudWatchException e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
    }
```

请参阅上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/f4eaf2b2971805cfb2b87a8e5ab408f83169432e/javav2/example_code/cloudwatch/src/main/java/com/example/cloudwatch/PutMetricAlarm.java) GitHub。

## 列出警报
<a name="list-alarms"></a>

要列出您创建 CloudWatchClient的 CloudWatch 警报，请使用可以用来设置结果选项的's `describeAlarms` 方法。[DescribeAlarmsRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/cloudwatch/model/DescribeAlarmsRequest.html)

 **导入** 

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.cloudwatch.CloudWatchClient;
import software.amazon.awssdk.services.cloudwatch.model.CloudWatchException;
import software.amazon.awssdk.services.cloudwatch.model.DescribeAlarmsRequest;
import software.amazon.awssdk.services.cloudwatch.model.DescribeAlarmsResponse;
import software.amazon.awssdk.services.cloudwatch.model.MetricAlarm;
```

 **代码** 

```
    public static void desCWAlarms( CloudWatchClient cw) {

        try {

            boolean done = false;
            String newToken = null;

            while(!done) {
                DescribeAlarmsResponse response;

                if (newToken == null) {
                    DescribeAlarmsRequest request = DescribeAlarmsRequest.builder().build();
                    response = cw.describeAlarms(request);
                } else {
                    DescribeAlarmsRequest request = DescribeAlarmsRequest.builder()
                        .nextToken(newToken)
                        .build();
                    response = cw.describeAlarms(request);
                }

                for(MetricAlarm alarm : response.metricAlarms()) {
                    System.out.printf("\n Retrieved alarm %s", alarm.alarmName());
                }

                if(response.nextToken() == null) {
                    done = true;
                } else {
                    newToken = response.nextToken();
                }
            }

        } catch (CloudWatchException e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
        System.out.printf("Done");
    }
```

可以通过调用`MetricAlarms`返回的来获取警报列表`describeAlarms`。[DescribeAlarmsResponse](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/cloudwatch/model/DescribeAlarmsResponse.html)

结果可以*分页*。要检索下一批结果，请对响应对象调用 `nextToken` 并使用该令牌值构建新的请求对象。然后使用新请求再次调用 `describeAlarms` 方法。

**注意**  
您还可以使用的`describeAlarmsForMetric`方法检索特定指标 CloudWatchClient的警报。它的使用类似于 `describeAlarms`。

请参阅上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/f4eaf2b2971805cfb2b87a8e5ab408f83169432e/javav2/example_code/cloudwatch/src/main/java/com/example/cloudwatch/DescribeAlarms.java) GitHub。

## 删除警报
<a name="delete-alarms"></a>

要删除 CloudWatch 警报，请使用[DeleteAlarmsRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/cloudwatch/model/DeleteAlarmsRequest.html)包含一个或多个要删除的警报名称的调用`deleteAlarms`方法。 CloudWatchClient

 **导入** 

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.cloudwatch.CloudWatchClient;
import software.amazon.awssdk.services.cloudwatch.model.CloudWatchException;
import software.amazon.awssdk.services.cloudwatch.model.DeleteAlarmsRequest;
```

 **代码** 

```
    public static void deleteCWAlarm(CloudWatchClient cw, String alarmName) {

        try {
            DeleteAlarmsRequest request = DeleteAlarmsRequest.builder()
                    .alarmNames(alarmName)
                    .build();

            cw.deleteAlarms(request);
            System.out.printf("Successfully deleted alarm %s", alarmName);

        } catch (CloudWatchException e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
    }
```

请参阅上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/f4eaf2b2971805cfb2b87a8e5ab408f83169432e/javav2/example_code/cloudwatch/src/main/java/com/example/cloudwatch/DeleteAlarm.java) GitHub。

## 更多信息
<a name="more-information"></a>
+  [使用 Amazon CloudWatch 用户指南中的 Amazon CloudWatch 警报](https://docs.aws.amazon.com//AmazonCloudWatch/latest/monitoring/AlarmThatSendsEmail.html)
+  [PutMetricAlarm](https://docs.aws.amazon.com//AmazonCloudWatch/latest/APIReference/API_PutMetricAlarm.html)在 Amazon CloudWatch API 参考中
+  [DescribeAlarms](https://docs.aws.amazon.com//AmazonCloudWatch/latest/APIReference/API_DescribeAlarms.html)在 Amazon CloudWatch API 参考中
+  [DeleteAlarms](https://docs.aws.amazon.com//AmazonCloudWatch/latest/APIReference/API_DeleteAlarms.html)在 Amazon CloudWatch API 参考中

# 使用 Amazon CloudWatch 活动
<a name="examples-cloudwatch-send-events"></a>

 CloudWatch 事件提供近乎实时的系统事件流，这些事件描述了 Amazon EC2 实例、 Lambda 函数、 Kinesis 流、 Amazon ECS 任务、 Step Functions 状态机、 Amazon SNS 主题、 Amazon SQS 队列或内置目标的 AWS 资源变化。通过使用简单的规则，您可以匹配事件并将事件路由到一个或多个目标函数或流。

Amazon EventBridge 是 CloudWatch 活动的[演变](https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-cwe-now-eb.html)。这两项服务使用相同的 API，因此您可以继续使用 SDK 提供的[CloudWatch 事件客户端](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/cloudwatch/CloudWatchClient.html)，也可以迁移到适用于 Java 的 SDK CloudWatch 的事件[EventBridge 客户端](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/eventbridge/EventBridgeClient.html)功能。 CloudWatch 活动[用户指南文档](https://docs.aws.amazon.com/eventbridge/latest/userguide/index.html)和 [API 参考](https://docs.aws.amazon.com/eventbridge/latest/APIReference/index.html)现在可通过 EventBridge 文档网站获得。

## 添加事件
<a name="add-events"></a>

要添加自定义 CloudWatch 事件，请使用包含一个[https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/cloudwatchevents/model/PutEventsRequest.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/cloudwatchevents/model/PutEventsRequest.html)或多个提供每个事件详细信息的[https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/cloudwatchevents/model/PutEventsRequestEntry.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/cloudwatchevents/model/PutEventsRequestEntry.html)对象来调用该`CloudWatchEventsClient’s``putEvents`方法。您可以为条目指定多个参数，例如事件的来源和类型、与事件相关联的资源等等。

**注意**  
对于每个 `putEvents` 调用，您最多可以指定 10 个事件。

 **导入** 

```
import software.amazon.awssdk.services.cloudwatch.model.CloudWatchException;
import software.amazon.awssdk.services.cloudwatchevents.CloudWatchEventsClient;
import software.amazon.awssdk.services.cloudwatchevents.model.PutEventsRequest;
import software.amazon.awssdk.services.cloudwatchevents.model.PutEventsRequestEntry;
```

 **代码** 

```
    public static void putCWEvents(CloudWatchEventsClient cwe, String resourceArn ) {

        try {

            final String EVENT_DETAILS =
                "{ \"key1\": \"value1\", \"key2\": \"value2\" }";

            PutEventsRequestEntry requestEntry = PutEventsRequestEntry.builder()
                    .detail(EVENT_DETAILS)
                    .detailType("sampleSubmitted")
                    .resources(resourceArn)
                    .source("aws-sdk-java-cloudwatch-example")
                    .build();

            PutEventsRequest request = PutEventsRequest.builder()
                    .entries(requestEntry)
                    .build();

            cwe.putEvents(request);
            System.out.println("Successfully put CloudWatch event");

        } catch (CloudWatchException e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
    }
```

请参阅上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/0b1785e42949ebf959eaa0f0da4dc2a48f92ea25/javav2/example_code/cloudwatch/src/main/java/com/example/cloudwatch/PutEvents.java) GitHub。

## 添加规则
<a name="add-rules"></a>

要创建或更新规则，请使用[https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/cloudwatchevents/model/PutRuleRequest.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/cloudwatchevents/model/PutRuleRequest.html)具有规则名称和可选参数（例如[事件模式](https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-event-patterns.html)、要与规则关联的 IAM 角色以及描述规则运行频率的[调度表达式](https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-create-rule-schedule.html)）来调用`CloudWatchEventsClient’s``putRule`方法。

 **导入** 

```
import software.amazon.awssdk.services.cloudwatch.model.CloudWatchException;
import software.amazon.awssdk.services.cloudwatchevents.CloudWatchEventsClient;
import software.amazon.awssdk.services.cloudwatchevents.model.PutRuleRequest;
import software.amazon.awssdk.services.cloudwatchevents.model.PutRuleResponse;
import software.amazon.awssdk.services.cloudwatchevents.model.RuleState;
```

 **代码** 

```
    public static void putCWRule(CloudWatchEventsClient cwe, String ruleName, String roleArn) {

        try {
            PutRuleRequest request = PutRuleRequest.builder()
                .name(ruleName)
                .roleArn(roleArn)
                .scheduleExpression("rate(5 minutes)")
                .state(RuleState.ENABLED)
                .build();

            PutRuleResponse response = cwe.putRule(request);
            System.out.printf(
                    "Successfully created CloudWatch events rule %s with arn %s",
                    roleArn, response.ruleArn());
        } catch (
            CloudWatchException e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
    }
```

请参阅上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/0b1785e42949ebf959eaa0f0da4dc2a48f92ea25/javav2/example_code/cloudwatch/src/main/java/com/example/cloudwatch/PutRule.java) GitHub。

## 添加目标
<a name="add-targets"></a>

目标是触发规则时调用的资源。示例目标包括 Amazon EC2 实例、 Lambda 函数、 Kinesis 流、 Amazon ECS 任务、 Step Functions 状态机和内置目标。

要向规则添加目标，请使用 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/cloudwatchevents/model/PutTargetsRequest.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/cloudwatchevents/model/PutTargetsRequest.html)（包含要更新的规则和要添加到规则的目标列表）来调用 `CloudWatchEventsClient’s` `putTargets` 方法。

 **导入** 

```
import software.amazon.awssdk.services.cloudwatch.model.CloudWatchException;
import software.amazon.awssdk.services.cloudwatchevents.CloudWatchEventsClient;
import software.amazon.awssdk.services.cloudwatchevents.model.PutTargetsRequest;
import software.amazon.awssdk.services.cloudwatchevents.model.PutTargetsResponse;
import software.amazon.awssdk.services.cloudwatchevents.model.Target;
```

 **代码** 

```
    public static void putCWTargets(CloudWatchEventsClient cwe, String ruleName, String functionArn, String targetId ) {

        try {
            Target target = Target.builder()
                .arn(functionArn)
                .id(targetId)
                .build();

            PutTargetsRequest request = PutTargetsRequest.builder()
                .targets(target)
                .rule(ruleName)
                .build();

            PutTargetsResponse response = cwe.putTargets(request);
            System.out.printf(
                "Successfully created CloudWatch events target for rule %s",
                ruleName);
        } catch (CloudWatchException e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
    }
```

请参阅上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/0b1785e42949ebf959eaa0f0da4dc2a48f92ea25/javav2/example_code/cloudwatch/src/main/java/com/example/cloudwatch/PutTargets.java) GitHub。

## 更多信息
<a name="more-information"></a>
+  在 Amazon EventBridge 用户指南 PutEvents中使用@@ [添加事件](https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-putevents.html)
+  在 Amazon EventBridge 用户指南中@@ [安排规则表达式](https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-create-rule-schedule.html#eb-create-scheduled-rule-schedule)
+  Amazon EventBridge 用户指南 CloudWatch Events中的@@ [事件类型](https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-service-event.html)
+  Amazon EventBridge 用户指南中的@@ [事件模式](https://docs.aws.amazon.com/eventbridge/latest/userguide/eb-event-patterns.html)
+  [PutEvents](https://docs.aws.amazon.com/eventbridge/latest/APIReference/API_PutEvents.html)在 Amazon EventBridge API 参考中
+  [PutTargets](https://docs.aws.amazon.com/eventbridge/latest/APIReference/API_PutTargets.html)在 Amazon EventBridge API 参考中
+  [PutRule](https://docs.aws.amazon.com/eventbridge/latest/APIReference/API_PutRule.html)在 Amazon EventBridge API 参考中

# AWS 数据库服务和AWS SDK for Java 2.x
<a name="examples-databases"></a>

AWS 提供了多种数据库类型：关系数据库、键值数据库、内存数据库、文档数据库和[其他几种数据库](https://aws.amazon.com/products/databases/)。适用于 Java 的 SDK 2.x 支持因 AWS 中数据库服务的性质而异。

某些数据库服务（例如 [Amazon DynamoDB](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/Welcome.html) 服务）具有用于管理 AWS 资源（数据库）的 Web 服务 API 以及用于与数据交互的 Web 服务 API。在适用于 Java 的 SDK 2.x 中，这些类型的服务有专用的服务客户端，例如 [DynamoDBClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/DynamoDbClient.html)。

其他数据库服务具有与资源交互的 Web 服务 API，例如 [Amazon DocumentDB](https://docs.aws.amazon.com/documentdb/latest/developerguide/api-reference.html) API（用于集群、实例和资源管理），但没有用于处理数据的 Web 服务 API。适用于 Java 的 SDK 2.x 具有相应的 [DocDbClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/docdb/DocDbClient.html) 接口，用于处理资源。但是，您需要另一个 Java API，比如[适用于 Java 的 MongoDB](https://www.mongodb.com/developer/languages/java/) 来处理数据。

请使用以下示例来了解如何将适用于 Java 的 SDK 2.x 的服务客户端与不同类型的数据库配合使用。

## Amazon DynamoDB 示例
<a name="examples-db-dynamodb"></a>


| 处理数据 | 使用数据库 | 
| --- |--- |
| SDK service client: [DynamoDbClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/DynamoDbClient.html) | SDK service client: [DynamoDbClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/DynamoDbClient.html) | 
| Example: [使用 DynamoDB 的 React/Spring REST 应用程序](https://github.com/awsdocs/aws-doc-sdk-examples/tree/main/javav2/usecases/creating_dynamodb_web_app) | Examples: [CreateTable、ListTables、DeleteTable](https://github.com/awsdocs/aws-doc-sdk-examples/tree/main/javav2/example_code/dynamodb/src/main/java/com/example/dynamodb) | 
| Examples: [几个 DynamoDB 示例](https://github.com/awsdocs/aws-doc-sdk-examples/tree/main/javav2/example_code/dynamodb/src/main/java/com/example/dynamodb) |  | 
|  | 
| --- |
| SDK service client: [DynamoDbEnhancedClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedClient.html) |  | 
| Example: [使用 DynamoDB 的 React/Spring REST 应用程序](https://github.com/awsdocs/aws-doc-sdk-examples/tree/main/javav2/usecases/creating_dynamodb_web_app) |  | 
| Examples: [几个 DynamoDB 示例](https://github.com/awsdocs/aws-doc-sdk-examples/tree/main/javav2/example_code/dynamodb/src/main/java/com/example/dynamodb) (names starting with 'Enhanced") |  | 

请在本指南的指导性代码示例部分查看[其他 DynamoDB 示例](examples-dynamodb.md)。

## Amazon RDS 示例
<a name="examples-db-rds"></a>


|  处理数据  |  使用数据库  | 
| --- | --- | 
| 非 SDK API：JDBC，特定于数据库的 SQL 风格；您的代码管理数据库连接或连接池。 | SDK 服务客户端：[RdsClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/rds/RdsClient.html) | 
| 示例：[使用 MySQL 的 React/Spring REST 应用程序](https://github.com/awsdocs/aws-doc-sdk-examples/tree/main/javav2/usecases/Creating_rds_item_tracker) | 示例：[几个 RdsClient 示例](https://github.com/awsdocs/aws-doc-sdk-examples/tree/main/javav2/example_code/rds/src/main/java/com/example/rds) | 

## Amazon Redshift 示例
<a name="examples-db-redshift"></a>


|  处理数据  |  使用数据库  | 
| --- | --- | 
| SDK 服务客户端：[RedshiftDataClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/redshiftdata/RedshiftDataClient.html) | SDK 服务客户端：[RedshiftClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/redshift/RedshiftClient.html) | 
| 示例：[几个 RedshiftDataClient 示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/c682a07a1e6abce793e3c32ef3b9661fa723d0ff/javav2/example_code/redshift/src/main/java/com/example/scenario/RedshiftScenario.java) | 示例：[几个 RedshiftClient 示例](https://github.com/awsdocs/aws-doc-sdk-examples/tree/main/javav2/example_code/redshift/src/main/java/com/example/redshift) | 
| 示例：[使用 RedshiftDataClient 的 React/Spring REST 应用程序](https://github.com/awsdocs/aws-doc-sdk-examples/tree/main/javav2/usecases/CreatingSpringRedshiftRest) |  | 

## Amazon Aurora Serverless v2 示例
<a name="examples-db-aurora-sv1"></a>


|  处理数据  |  使用数据库  | 
| --- | --- | 
| SDK 服务客户端：[RdsDataClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/rdsdata/RdsDataClient.html) | SDK 服务客户端：[RdsClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/rds/RdsClient.html) | 
| 示例：[使用 RdsDataClient 的 React/Spring REST 应用程序](https://github.com/awsdocs/aws-doc-sdk-examples/tree/main/javav2/usecases/Creating_Spring_RDS_Rest) | 示例：[几个 RdsClient 示例](https://github.com/awsdocs/aws-doc-sdk-examples/tree/main/javav2/example_code/rds/src/main/java/com/example/rds) | 

## Amazon DocumentDB 示例
<a name="examples-db-docdb"></a>


|  处理数据  |  使用数据库  | 
| --- | --- | 
| 非 SDK API：特定于 MongoDB 的 Java 库（例如 [MongoDB for Java](https://www.mongodb.com/developer/languages/java/)）；您的代码管理数据库连接或连接池。 | SDK 服务客户端：[DocDbClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/docdb/DocDbClient.html) | 
| 示例：[DocumentDB (Mongo) Developer Guide](https://docs.aws.amazon.com/documentdb/latest/developerguide/connect_programmatically.html#connect_programmatically-tls_enabled)（选择“Java”标签） |  | 

# 与... 一起工作 DynamoDB
<a name="examples-dynamodb"></a>

[DynamoDB](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Introduction.html) 是一项完全托管式 NoSQL 数据库服务，提供快速且可预测的性能，能够实现无缝扩展。本节向您展示如何使用 适用于 Java 的 AWS SDK 2.x 来处理 DynamoDB。

## 选择您的 DynamoDB 客户端
<a name="ddb-clients-overview"></a>

SDK 提供了两种处理 DynamoDB 的主要方法：

低级别客户端（`DynamoDbClient`）  
通过完全控制请求和响应，提供对 DynamoDB 操作的直接访问。当您需要精细控制或使用动态架构时，请使用此客户端。

增强型客户端（`DynamoDbEnhancedClient`）  
通过 Java 对象和 DynamoDB 项目之间的自动映射，提供面向对象的编程。还提供了面向文档的功能，用于处理不遵循固定架构且类似 JSON 的数据。在处理定义明确的数据模型或文档类型数据时，请使用此客户端。

## 配置 DynamoDB 客户端
<a name="ddb-configuration-setup"></a>

在使用 DynamoDB 之前，请配置您的客户端以获得更好的性能和可靠性。

### 了解 DynamoDB 重试行为
<a name="ddb-retry-behavior"></a>

DynamoDB 客户端使用的默认最大重试次数为 8，该值高于其他 AWS 服务 客户端。这种较高的重试次数有助于应对 DynamoDB 的分布式特性和临时容量限制。有关重试策略的更多信息，请参阅[在中配置重试行为 AWS SDK for Java 2.x](retry-strategy.md)。

### 使用基于账户的端点来优化性能
<a name="ddb-account-based-endpoints-v2"></a>

DynamoDB [AWS 提供基于账户的](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Programming.SDKOverview.html#Programming.SDKs.endpoints)终端节点，通过使用 AWS 您的账户 ID 来简化请求路由，从而提高性能。

要使用此功能，您需要使用 AWS SDK for Java 2.x的版本 2.28.4 或更高版本。您可以在 [Maven Central 存储库](https://central.sonatype.com/artifact/software.amazon.awssdk/bom/versions)中找到最新版本。支持的 SDK 版本会自动使用新的端点。

要选择退出基于账户的路由，请选择以下选项之一：
+ 配置 DynamoDB 服务客户端，将 `AccountIdEndpointMode` 设置为 `DISABLED`。
+ 设置环境变量。
+ 设置 JVM 系统属性。
+ 更新共享 AWS 配置文件设置。

以下示例演示了如何通过配置 DynamoDB 服务客户端来禁用基于账户的路由：

```
DynamoDbClient.builder()
                 .accountIdEndpointMode(AccountIdEndpointMode.DISABLED)
                 .build();
```

有关其他配置选项的更多信息，请参阅《工具参考指南》中的[基于账户 AWS SDKs 的终端节点](https://docs.aws.amazon.com/sdkref/latest/guide/feature-account-endpoints.html)。

## 本主题涵盖的内容
<a name="ddb-topics-overview"></a>

以下各节将向您展示如何使用 DynamoDB：
+ [处理 DynamoDB 中的表](examples-dynamodb-tables.md) - 创建、描述、更新和删除表
+ [处理中的项目 DynamoDB](examples-dynamodb-items.md) - 添加、检索和更新单个项目
+ [使用 Java 对象映射到 DynamoDB 项目 AWS SDK for Java 2.x](dynamodb-enhanced-client.md) - 在增强型客户端中使用对象映射和面向文档的数据

有关其他 DynamoDB 代码示例，请参阅代码示例库中的 [DynamoDB 代码示](java_dynamodb_code_examples.md)例。 AWS 

# 处理 DynamoDB 中的表
<a name="examples-dynamodb-tables"></a>

表是 DynamoDB 数据库中所有项目的容器。您必须先创建表，然后才能在 DynamoDB 中添加或删除数据。

对于每个表，您必须定义：
+ 表*名称*，它对于您的账户和所在区域是唯一的。
+ 一个*主键*，每个值对于它都必须是唯一的；表中的任意两个项目不能具有相同的主键值。

  主键可以是*简单*主键（包含单个分区 (HASH) 键）或*复合*主键（包含一个分区和一个排序 (RANGE) 键）。

  每个键值均有一个由 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/ScalarAttributeType.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/ScalarAttributeType.html) 类枚举的关联*数据类型*。键值可以是二进制 (B)、数字 (N) 或字符串 (S)。有关更多信息，请参阅《Amazon DynamoDB 开发人员指南》中的[命名规则和数据类型](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html)。
+  *预置吞吐量* 是定义为表保留的读取/写入容量单位数的值。
**注意**  
 [Amazon DynamoDB 定价](https://aws.amazon.com/dynamodb/pricing/)基于您为表设置的预置吞吐量值，因此您应只为表保留可能需要的容量。

  表的预置吞吐量可随时修改，以便您能够在需要更改时调整容量。

## 创建表
<a name="dynamodb-create-table"></a>

使用 `DynamoDbClient’s` `createTable` 方法来创建新的 DynamoDB 表。您需要构造表属性和表架构，二者用于标识表的主键。您还必须提供初始预置吞吐量值和表名。

**注意**  
如果使用您所选名称的表已存在，则将引发 `[DynamoDbException](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/DynamoDbException.html)`。

### 创建具有简单主键的表
<a name="dynamodb-create-table-simple"></a>

此代码创建一个表，该表只有一个属性，这个属性是表的简单主键。该示例为 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/CreateTableRequest.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/CreateTableRequest.html) 使用 `[AttributeDefinition](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/AttributeDefinition.html)` 和 `[KeySchemaElement](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/KeySchemaElement.html)` 对象。

 **导入**。

```
import software.amazon.awssdk.core.waiters.WaiterResponse;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.model.CreateTableRequest;
import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition;
import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType;
import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType;
import software.amazon.awssdk.services.dynamodb.model.KeySchemaElement;
import software.amazon.awssdk.services.dynamodb.model.ProvisionedThroughput;
import software.amazon.awssdk.services.dynamodb.model.KeyType;
import software.amazon.awssdk.services.dynamodb.model.CreateTableResponse;
import software.amazon.awssdk.services.dynamodb.model.DescribeTableRequest;
import software.amazon.awssdk.services.dynamodb.model.DescribeTableResponse;
import software.amazon.awssdk.services.dynamodb.model.DynamoDbException;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.waiters.DynamoDbWaiter;
```

 **代码** 

```
    public static String createTable(DynamoDbClient ddb, String tableName, String key) {

        DynamoDbWaiter dbWaiter = ddb.waiter();
        CreateTableRequest request = CreateTableRequest.builder()
                .attributeDefinitions(AttributeDefinition.builder()
                        .attributeName(key)
                        .attributeType(ScalarAttributeType.S)
                        .build())
                .keySchema(KeySchemaElement.builder()
                        .attributeName(key)
                        .keyType(KeyType.HASH)
                        .build())
                .provisionedThroughput(ProvisionedThroughput.builder()
                        .readCapacityUnits(new Long(10))
                        .writeCapacityUnits(new Long(10))
                        .build())
                .tableName(tableName)
                .build();

        String newTable ="";
        try {
            CreateTableResponse response = ddb.createTable(request);
            DescribeTableRequest tableRequest = DescribeTableRequest.builder()
                    .tableName(tableName)
                    .build();

            // Wait until the Amazon DynamoDB table is created
            WaiterResponse<DescribeTableResponse> waiterResponse =  dbWaiter.waitUntilTableExists(tableRequest);
            waiterResponse.matched().response().ifPresent(System.out::println);

            newTable = response.tableDescription().tableName();
            return newTable;

        } catch (DynamoDbException e) {
            System.err.println(e.getMessage());
            System.exit(1);
        }
       return "";
    }
```

请参阅 GitHub 上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/0b1785e42949ebf959eaa0f0da4dc2a48f92ea25/javav2/example_code/dynamodb/src/main/java/com/example/dynamodb/CreateTable.java)。

### 创建具有复合主键的表
<a name="dynamodb-create-table-composite"></a>

以下示例创建带有两个属性的表。这两个属性均用于复合主键。

 **导入**。

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.model.DynamoDbException;
import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition;
import software.amazon.awssdk.services.dynamodb.model.CreateTableRequest;
import software.amazon.awssdk.services.dynamodb.model.CreateTableResponse;
import software.amazon.awssdk.services.dynamodb.model.KeySchemaElement;
import software.amazon.awssdk.services.dynamodb.model.KeyType;
import software.amazon.awssdk.services.dynamodb.model.ProvisionedThroughput;
import software.amazon.awssdk.services.dynamodb.model.ScalarAttributeType;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
```

 **代码** 

```
    public static String createTableComKey(DynamoDbClient ddb, String tableName) {
        CreateTableRequest request = CreateTableRequest.builder()
                .attributeDefinitions(
                        AttributeDefinition.builder()
                                .attributeName("Language")
                                .attributeType(ScalarAttributeType.S)
                                .build(),
                        AttributeDefinition.builder()
                                .attributeName("Greeting")
                                .attributeType(ScalarAttributeType.S)
                                .build())
                .keySchema(
                        KeySchemaElement.builder()
                                .attributeName("Language")
                                .keyType(KeyType.HASH)
                                .build(),
                        KeySchemaElement.builder()
                                .attributeName("Greeting")
                                .keyType(KeyType.RANGE)
                                .build())
                .provisionedThroughput(
                        ProvisionedThroughput.builder()
                                .readCapacityUnits(new Long(10))
                                .writeCapacityUnits(new Long(10)).build())
                .tableName(tableName)
                .build();

       String tableId = "";

       try {
            CreateTableResponse result = ddb.createTable(request);
            tableId = result.tableDescription().tableId();
            return tableId;
        } catch (DynamoDbException e) {
            System.err.println(e.getMessage());
            System.exit(1);
        }
       return "";
    }
```

请参阅 GitHub 上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/0b1785e42949ebf959eaa0f0da4dc2a48f92ea25/javav2/example_code/dynamodb/src/main/java/com/example/dynamodb/CreateTableCompositeKey.java)。

## 列出表
<a name="dynamodb-list-tables"></a>

您可以通过调用 `DynamoDbClient’s` `listTables` 方法列出特定区域中的表。

**注意**  
如果您的账户和区域没有使用该名称的表，则将引发 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/ResourceNotFoundException.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/ResourceNotFoundException.html) 异常。

 **导入**。

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.model.DynamoDbException;
import software.amazon.awssdk.services.dynamodb.model.ListTablesResponse;
import software.amazon.awssdk.services.dynamodb.model.ListTablesRequest;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import java.util.List;
```

 **代码** 

```
    public static void listAllTables(DynamoDbClient ddb){

        boolean moreTables = true;
        String lastName = null;

        while(moreTables) {
            try {
                ListTablesResponse response = null;
                if (lastName == null) {
                    ListTablesRequest request = ListTablesRequest.builder().build();
                    response = ddb.listTables(request);
                } else {
                    ListTablesRequest request = ListTablesRequest.builder()
                            .exclusiveStartTableName(lastName).build();
                    response = ddb.listTables(request);
                }

                List<String> tableNames = response.tableNames();

                if (tableNames.size() > 0) {
                    for (String curName : tableNames) {
                        System.out.format("* %s\n", curName);
                    }
                } else {
                    System.out.println("No tables found!");
                    System.exit(0);
                }

                lastName = response.lastEvaluatedTableName();
                if (lastName == null) {
                    moreTables = false;
                }
            } catch (DynamoDbException e) {
                System.err.println(e.getMessage());
                System.exit(1);
            }
        }
        System.out.println("\nDone!");
    }
```

默认情况下，每次调用将返回最多 100 个表 – 对返回的 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/ListTablesResponse.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/ListTablesResponse.html) 对象使用 `lastEvaluatedTableName` 可获得评估的上一个表。可使用此值在上一列出的最后一个返回值后开始列出。

请参阅 GitHub 上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/0b1785e42949ebf959eaa0f0da4dc2a48f92ea25/javav2/example_code/dynamodb/src/main/java/com/example/dynamodb/ListTables.java)。

## 描述表（获取相关信息）
<a name="dynamodb-describe-table"></a>

使用 `DynamoDbClient’s` `describeTable` 方法获取有关表的信息。

**注意**  
如果您的账户和区域没有使用该名称的表，则将引发 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/ResourceNotFoundException.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/ResourceNotFoundException.html) 异常。

 **导入**。

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.model.DynamoDbException;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.AttributeDefinition;
import software.amazon.awssdk.services.dynamodb.model.DescribeTableRequest;
import software.amazon.awssdk.services.dynamodb.model.ProvisionedThroughputDescription;
import software.amazon.awssdk.services.dynamodb.model.TableDescription;
import java.util.List;
```

 **代码** 

```
    public static void describeDymamoDBTable(DynamoDbClient ddb,String tableName ) {

        DescribeTableRequest request = DescribeTableRequest.builder()
                .tableName(tableName)
                .build();

        try {
            TableDescription tableInfo =
                    ddb.describeTable(request).table();

            if (tableInfo != null) {
                System.out.format("Table name  : %s\n",
                        tableInfo.tableName());
                System.out.format("Table ARN   : %s\n",
                        tableInfo.tableArn());
                System.out.format("Status      : %s\n",
                        tableInfo.tableStatus());
                System.out.format("Item count  : %d\n",
                        tableInfo.itemCount().longValue());
                System.out.format("Size (bytes): %d\n",
                        tableInfo.tableSizeBytes().longValue());

                ProvisionedThroughputDescription throughputInfo =
                        tableInfo.provisionedThroughput();
                System.out.println("Throughput");
                System.out.format("  Read Capacity : %d\n",
                        throughputInfo.readCapacityUnits().longValue());
                System.out.format("  Write Capacity: %d\n",
                        throughputInfo.writeCapacityUnits().longValue());

                List<AttributeDefinition> attributes =
                        tableInfo.attributeDefinitions();
                System.out.println("Attributes");

                for (AttributeDefinition a : attributes) {
                    System.out.format("  %s (%s)\n",
                            a.attributeName(), a.attributeType());
                }
            }
        } catch (DynamoDbException e) {
            System.err.println(e.getMessage());
            System.exit(1);
        }
        System.out.println("\nDone!");
    }
```

请参阅 GitHub 上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/0b1785e42949ebf959eaa0f0da4dc2a48f92ea25/javav2/example_code/dynamodb/src/main/java/com/example/dynamodb/DescribeTable.java)。

## 修改（更新）表
<a name="dynamodb-update-table"></a>

您可以通过调用 `DynamoDbClient’s` `updateTable` 方法随时修改表的预置吞吐量值。

**注意**  
如果您的账户和区域没有使用该名称的表，则将引发 [ResourceNotFoundException](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/ResourceNotFoundException.html) 异常。

 **导入**。

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.model.ProvisionedThroughput;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.UpdateTableRequest;
import software.amazon.awssdk.services.dynamodb.model.DynamoDbException;
```

 **代码** 

```
    public static void updateDynamoDBTable(DynamoDbClient ddb,
                                           String tableName,
                                           Long readCapacity,
                                           Long writeCapacity) {

        System.out.format(
                "Updating %s with new provisioned throughput values\n",
                tableName);
        System.out.format("Read capacity : %d\n", readCapacity);
        System.out.format("Write capacity : %d\n", writeCapacity);

        ProvisionedThroughput tableThroughput = ProvisionedThroughput.builder()
                .readCapacityUnits(readCapacity)
                .writeCapacityUnits(writeCapacity)
                .build();

        UpdateTableRequest request = UpdateTableRequest.builder()
                .provisionedThroughput(tableThroughput)
                .tableName(tableName)
                .build();

        try {
            ddb.updateTable(request);
        } catch (DynamoDbException e) {
            System.err.println(e.getMessage());
            System.exit(1);
        }

        System.out.println("Done!");
    }
```

请参阅 GitHub 上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/0b1785e42949ebf959eaa0f0da4dc2a48f92ea25/javav2/example_code/dynamodb/src/main/java/com/example/dynamodb/UpdateTable.java)。

## 删除表
<a name="dynamodb-delete-table"></a>

要删除表，请调用 `DynamoDbClient’s` `deleteTable` 方法并提供表名称。

**注意**  
如果您的账户和区域没有使用该名称的表，则将引发 [ResourceNotFoundException](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/ResourceNotFoundException.html) 异常。

 **导入**。

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.model.DynamoDbException;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.DeleteTableRequest;
```

 **代码** 

```
    public static void deleteDynamoDBTable(DynamoDbClient ddb, String tableName) {

        DeleteTableRequest request = DeleteTableRequest.builder()
                .tableName(tableName)
                .build();

        try {
            ddb.deleteTable(request);

        } catch (DynamoDbException e) {
            System.err.println(e.getMessage());
            System.exit(1);
        }
        System.out.println(tableName +" was successfully deleted!");
    }
```

请参阅 GitHub 上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/0b1785e42949ebf959eaa0f0da4dc2a48f92ea25/javav2/example_code/dynamodb/src/main/java/com/example/dynamodb/DeleteTable.java)。

## 更多信息
<a name="more-information"></a>
+  《Amazon DynamoDB 开发人员指南》中的[表处理准则](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GuidelinesForTables.html)
+  《Amazon DynamoDB 开发人员指南》中的[处理 DynamoDB 中的表](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/WorkingWithTables.html)

# 处理中的项目 DynamoDB
<a name="examples-dynamodb-items"></a>

在中 DynamoDB，项目是*属性的集合，每个属性*都有一个*名称*和一个*值*。属性值可以为标量、集或文档类型。有关更多信息，请参阅《 Amazon DynamoDB 开发人员指南》中的[命名规则和数据类型](https://docs.aws.amazon.com//amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html)。

## 检索（获取）表中的项目
<a name="dynamodb-get-item"></a>

调用 DynamoDbClient's `getItem` 方法并向其传递一个包含所需项目的表名和主键值的[GetItemRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/GetItemRequest.html)对象。它返回一个包含该项目所有属性的[GetItemResponse](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/GetItemResponse.html)对象。您可以在 [ 中指定一个或多个](https://docs.aws.amazon.com//amazondynamodb/latest/developerguide/Expressions.ProjectionExpressions.html)投影表达式`GetItemRequest`以检索特定属性。

您可以使用返回`GetItemResponse`对象的`item()`方法来检索与该项目关联的键（字符串 [AttributeValue](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/AttributeValue.html)）和值（）对的[映射](https://docs.oracle.com/javase/8/docs/api/index.html?java/util/Map.html)。

 **导入** 

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.model.DynamoDbException;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
import software.amazon.awssdk.services.dynamodb.model.GetItemRequest;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
```

 **代码** 

```
    public static void getDynamoDBItem(DynamoDbClient ddb,String tableName,String key,String keyVal ) {

        HashMap<String,AttributeValue> keyToGet = new HashMap<String,AttributeValue>();

        keyToGet.put(key, AttributeValue.builder()
                .s(keyVal).build());

        GetItemRequest request = GetItemRequest.builder()
                .key(keyToGet)
                .tableName(tableName)
                .build();

        try {
            Map<String,AttributeValue> returnedItem = ddb.getItem(request).item();

            if (returnedItem != null) {
                Set<String> keys = returnedItem.keySet();
                System.out.println("Amazon DynamoDB table attributes: \n");

                for (String key1 : keys) {
                    System.out.format("%s: %s\n", key1, returnedItem.get(key1).toString());
                }
            } else {
                System.out.format("No item found with the key %s!\n", key);
            }
        } catch (DynamoDbException e) {
            System.err.println(e.getMessage());
            System.exit(1);
        }
    }
```

请参阅上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/bc964a243276990f05c180618ea8b34777c68f0e/javav2/example_code/dynamodb/src/main/java/com/example/dynamodb/GetItem.java) GitHub。

## 使用异步客户端从表中检索（获取）项目
<a name="id1ddb"></a>

调用`getItem`的方法， DynamoDbAsyncClient 然后向其传递一个包含所需项目的表名和主键值的[GetItemRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/GetItemRequest.html)对象。

您可以返回包含该项目的所有属性的 [Collection](https://docs.oracle.com/javase/8/docs/api/index.html?java/util/Collection.html) 实例（请参阅以下示例）。

 **导入** 

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.model.GetItemRequest;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
import software.amazon.awssdk.services.dynamodb.DynamoDbAsyncClient;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import software.amazon.awssdk.services.dynamodb.model.DynamoDbException;
```

 **代码** 

```
    public static void getItem(DynamoDbAsyncClient client, String tableName, String key,  String keyVal) {

        HashMap<String, AttributeValue> keyToGet =
                new HashMap<String, AttributeValue>();

        keyToGet.put(key, AttributeValue.builder()
                .s(keyVal).build());

        try {

            // Create a GetItemRequest instance
            GetItemRequest request = GetItemRequest.builder()
                    .key(keyToGet)
                    .tableName(tableName)
                    .build();

            // Invoke the DynamoDbAsyncClient object's getItem
            java.util.Collection<AttributeValue> returnedItem = client.getItem(request).join().item().values();

            // Convert Set to Map
            Map<String, AttributeValue> map = returnedItem.stream().collect(Collectors.toMap(AttributeValue::s, s->s));
            Set<String> keys = map.keySet();
            for (String sinKey : keys) {
                System.out.format("%s: %s\n", sinKey, map.get(sinKey).toString());
            }

        } catch (DynamoDbException e) {
            System.err.println(e.getMessage());
            System.exit(1);
        }
```

请参阅上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/bc964a243276990f05c180618ea8b34777c68f0e/javav2/example_code/dynamodbasync/src/main/java/com/example/dynamodbasync/DynamoDBAsyncGetItem.java) GitHub。

## 向表添加新项目
<a name="dynamodb-add-item"></a>

创建表示项目属性的键值对的[映射](https://docs.oracle.com/javase/8/docs/api/index.html?java/util/Map.html)。其中必须包括表的主键字段的值。如果主键标识的项目已存在，那么其字段将通过该请求*更新*。

**注意**  
如果您的账户和地区的命名表不存在，[ResourceNotFoundException](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/ResourceNotFoundException.html)则会抛出 a。

 **导入** 

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.model.DynamoDbException;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
import software.amazon.awssdk.services.dynamodb.model.PutItemRequest;
import software.amazon.awssdk.services.dynamodb.model.ResourceNotFoundException;
import java.util.HashMap;
```

 **代码** 

```
    public static void putItemInTable(DynamoDbClient ddb,
                                      String tableName,
                                      String key,
                                      String keyVal,
                                      String albumTitle,
                                      String albumTitleValue,
                                      String awards,
                                      String awardVal,
                                      String songTitle,
                                      String songTitleVal){

        HashMap<String,AttributeValue> itemValues = new HashMap<String,AttributeValue>();

        // Add all content to the table
        itemValues.put(key, AttributeValue.builder().s(keyVal).build());
        itemValues.put(songTitle, AttributeValue.builder().s(songTitleVal).build());
        itemValues.put(albumTitle, AttributeValue.builder().s(albumTitleValue).build());
        itemValues.put(awards, AttributeValue.builder().s(awardVal).build());

        PutItemRequest request = PutItemRequest.builder()
                .tableName(tableName)
                .item(itemValues)
                .build();

        try {
            ddb.putItem(request);
            System.out.println(tableName +" was successfully updated");

        } catch (ResourceNotFoundException e) {
            System.err.format("Error: The Amazon DynamoDB table \"%s\" can't be found.\n", tableName);
            System.err.println("Be sure that it exists and that you've typed its name correctly!");
            System.exit(1);
        } catch (DynamoDbException e) {
            System.err.println(e.getMessage());
            System.exit(1);
        }
    }
```

请参阅上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/f4eaf2b2971805cfb2b87a8e5ab408f83169432e/javav2/example_code/dynamodb/src/main/java/com/example/dynamodb/PutItem.java) GitHub。

## 更新表中现有项目
<a name="dynamodb-update-item"></a>

可以使用 DynamoDbClient 的 `updateItem` 方法，通过提供要更新的表名称、主键值和字段映射，更新表中已有项目的属性。

**注意**  
如果您的账户和地区的命名表不存在，或者您传入的主键标识的项目不存在，则会抛出 a [ResourceNotFoundException](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/ResourceNotFoundException.html)。

 **导入** 

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.model.DynamoDbException;
import software.amazon.awssdk.services.dynamodb.model.AttributeAction;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
import software.amazon.awssdk.services.dynamodb.model.AttributeValueUpdate;
import software.amazon.awssdk.services.dynamodb.model.ResourceNotFoundException;
import software.amazon.awssdk.services.dynamodb.model.UpdateItemRequest;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import java.util.HashMap;
```

 **代码** 

```
    public static void updateTableItem(DynamoDbClient ddb,
                                       String tableName,
                                       String key,
                                       String keyVal,
                                       String name,
                                       String updateVal){

        HashMap<String,AttributeValue> itemKey = new HashMap<String,AttributeValue>();

        itemKey.put(key, AttributeValue.builder().s(keyVal).build());

        HashMap<String,AttributeValueUpdate> updatedValues =
                new HashMap<String,AttributeValueUpdate>();

        // Update the column specified by name with updatedVal
        updatedValues.put(name, AttributeValueUpdate.builder()
                .value(AttributeValue.builder().s(updateVal).build())
                .action(AttributeAction.PUT)
                .build());

        UpdateItemRequest request = UpdateItemRequest.builder()
                .tableName(tableName)
                .key(itemKey)
                .attributeUpdates(updatedValues)
                .build();

        try {
            ddb.updateItem(request);
        } catch (ResourceNotFoundException e) {
            System.err.println(e.getMessage());
            System.exit(1);
        } catch (DynamoDbException e) {
            System.err.println(e.getMessage());
            System.exit(1);
        }

        System.out.println("Done!");
    }
```

请参阅上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/f4eaf2b2971805cfb2b87a8e5ab408f83169432e/javav2/example_code/dynamodb/src/main/java/com/example/dynamodb/UpdateItem.java) GitHub。

## 删除表中现有项目
<a name="dynamodb-delete-item"></a>

您可以通过使用 DynamoDbClient's `deleteItem` 方法并提供表名和主键值来删除表中存在的项目。

**注意**  
如果您的账户和地区的命名表不存在，或者您传入的主键标识的项目不存在，则会抛出 a [ResourceNotFoundException](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/ResourceNotFoundException.html)。

 **导入** 

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
import software.amazon.awssdk.services.dynamodb.model.DeleteItemRequest;
import software.amazon.awssdk.services.dynamodb.model.DynamoDbException;
import java.util.HashMap;
```

 **代码** 

```
  public static void deleteDynamoDBItem(DynamoDbClient ddb, String tableName, String key, String keyVal) {

        HashMap<String,AttributeValue> keyToGet =
                new HashMap<String,AttributeValue>();

        keyToGet.put(key, AttributeValue.builder()
                .s(keyVal)
                .build());

        DeleteItemRequest deleteReq = DeleteItemRequest.builder()
                .tableName(tableName)
                .key(keyToGet)
                .build();

        try {
            ddb.deleteItem(deleteReq);
        } catch (DynamoDbException e) {
            System.err.println(e.getMessage());
            System.exit(1);
        }
    }
```

请参阅上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/f4eaf2b2971805cfb2b87a8e5ab408f83169432e/javav2/example_code/dynamodb/src/main/java/com/example/dynamodb/DeleteItem.java) GitHub。

## 更多信息
<a name="more-information"></a>
+  《 Amazon DynamoDB 开发人员指南》中的[项目处理准则](https://docs.aws.amazon.com//amazondynamodb/latest/developerguide/best-practices.html)
+  [使用《 Amazon DynamoDB 开发者指南》 DynamoDB中的项目](https://docs.aws.amazon.com//amazondynamodb/latest/developerguide/WorkingWithItems.html)

# 使用 Java 对象映射到 DynamoDB 项目 AWS SDK for Java 2.x
<a name="dynamodb-enhanced-client"></a>

[DynamoDB 增强型客户端 API](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/package-summary.html) 是一个高级别库，是适用于 Java 的 SDK v1.x 中 `DynamoDBMapper` 类的后继库。它提供一种将客户端类映射到 DynamoDB 表的简单方法。您可以在代码中定义表与其相应数据类之间的关系。在定义这些关系后，您可以直观地对 DynamoDB 中的表或项目执行各种创建、读取、更新或删除（CRUD）操作。

DynamoDB 增强型客户端 API 还包括[增强型文档 API](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/document/package-summary.html)，使您能够处理不遵循已定义架构的文档类型项目。

**Topics**
+ [开始使用 DynamoDB 增强型客户端 API](ddb-en-client-getting-started.md)
+ [了解 DynamoDB 增强型客户端 API 的基础知识](ddb-en-client-use.md)
+ [使用高级映射功能](ddb-en-client-adv-features.md)
+ [使用适用于 DynamoDB 的增强型文档 API 处理 JSON 文档](ddb-en-client-doc-api.md)
+ [使用扩展自定义 DynamoDB 增强型客户端操作](ddb-en-client-extensions.md)
+ [异步使用 DynamoDB 增强型客户端 API](ddb-en-client-async.md)
+ [数据类注释](ddb-en-client-anno-index.md)

# 开始使用 DynamoDB 增强型客户端 API
<a name="ddb-en-client-getting-started"></a>

以下教程向您介绍了使用 DynamoDB 增强型客户端 API 所需的基础知识。

## 添加依赖项
<a name="ddb-en-client-gs-dep"></a>

要开始在项目中使用 DynamoDB 增强型客户端 API，请添加 Maven 构件 `dynamodb-enhanced` 的依赖项。如以下示例所示。

------
#### [ Maven ]

```
<project>
  <dependencyManagement>
   <dependencies>
      <dependency>
        <groupId>software.amazon.awssdk</groupId>
        <artifactId>bom</artifactId>
        <version><VERSION></version>
        <type>pom</type>
        <scope>import</scope>
      </dependency>
   </dependencies>
  </dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>software.amazon.awssdk</groupId>
      <artifactId>dynamodb-enhanced</artifactId>
    </dependency>
  </dependencies>
  ...
</project>
```

在 Maven 中央存储库中搜索[最新版本](https://central.sonatype.com/artifact/software.amazon.awssdk/bom)并*<VERSION>*替换为该值。

------
#### [ Gradle ]

```
repositories {
    mavenCentral()
}
dependencies {
    implementation(platform("software.amazon.awssdk:bom:<VERSION>"))
    implementation("software.amazon.awssdk:dynamodb-enhanced")
    ...
}
```

在 Maven 中央存储库中搜索[最新版本](https://central.sonatype.com/artifact/software.amazon.awssdk/bom)并*<VERSION>*替换为该值。

------

# 从数据类生成 `TableSchema`
<a name="ddb-en-client-gs-tableschema"></a>

`[TableSchema](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/TableSchema.html)` 使增强型客户端能够将 DynamoDB 属性值映射到您的客户端类，反之亦然。在本教程中，您将了解两类 `TableSchema`，一类是从静态数据类派生的，另一类是使用生成器从代码中生成的。

## 使用带注释的数据类
<a name="ddb-en-client-gs-tableschema-anno-bean"></a>

适用于 Java 的 SDK 2.x 包含[一组注释](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/mapper/annotations/package-summary.html)，您可以将其与数据类结合使用，以快速生成 `TableSchema` 来将类映射到表。

首先创建一个符合[JavaBean 规范](https://download.oracle.com/otn-pub/jcp/7224-javabeans-1.01-fr-spec-oth-JSpec/beans.101.pdf)的数据类。该规范要求类具有无参数的公共构造函数，并且类中的每个属性都有 getter 和 setter。包括类级别的注释，以指示数据类是 `DynamoDbBean`. 此外，至少要在 getter 或 setter 上包含关于主键属性的 `DynamoDbPartitionKey` 注释。

您可以对 getter 或 setter 应用[属性级注释](ddb-en-client-anno-index.md)，但不能同时应用两者。

**注意**  
该术语`property`通常用于封装在 a 中的值。 JavaBean但是，为了与 DynamoDB 使用的术语保持一致，本指南（英文版）改用术语 `attribute`。（中文版中，两者的翻译均为“属性”。）

以下 `Customer` 类展示了将类定义链接到 DynamoDB 表的注释。

### `Customer` 类
<a name="ddb-en-client-gs-tableschema-anno-bean-cust"></a>

```
package org.example.tests.model;

import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey;

import java.time.Instant;

@DynamoDbBean
public class Customer {

    private String id;
    private String name;
    private String email;
    private Instant regDate;

    @DynamoDbPartitionKey
    public String getId() { return this.id; }

    public void setId(String id) { this.id = id; }

    public String getCustName() { return this.name; }

    public void setCustName(String name) { this.name = name; }

    @DynamoDbSortKey
    public String getEmail() { return this.email; }

    public void setEmail(String email) { this.email = email; }

    public Instant getRegistrationDate() { return this.regDate; }

    public void setRegistrationDate(Instant registrationDate) { this.regDate = registrationDate; }

    @Override
    public String toString() {
        return "Customer [id=" + id + ", name=" + name + ", email=" + email
                + ", regDate=" + regDate + "]";
    }
}
```

创建带注释的数据类后，使用它来创建 `TableSchema`，如以下代码段所示。

```
static final TableSchema<Customer> customerTableSchema = TableSchema.fromBean(Customer.class);
```

`TableSchema` 被设计为静态且不可变。您通常可以在类加载时将其实例化。

静态 `TableSchema.fromBean()` 工厂方法对 bean 进行自检，生成数据类属性与 DynamoDB 属性之间的映射。

有关使用由多个数据类组成的数据模型的示例，请参阅[处理属性值为 bean、map、list 和 set 的对象](ddb-en-client-adv-features-nested.md)部分中的 `Person` 类。

## 使用生成器
<a name="ddb-en-client-gs-tableschema-builder"></a>

如果您在代码中定义表架构，则可以避免 Bean 自检的开销。如果您对架构进行编码，则您的类无需遵循 JavaBean 命名标准，也不需要对其进行注释。以下示例使用生成器，与使用注释的 `Customer` 类示例等效。

```
static final TableSchema<Customer> customerTableSchema =
                TableSchema.builder(Customer.class)
                        .newItemSupplier(Customer::new)
                        .addAttribute(String.class, a -> a.name("id")
                                .getter(Customer::getId)
                                .setter(Customer::setId)
                                .tags(StaticAttributeTags.primaryPartitionKey()))
                        .addAttribute(String.class, a -> a.name("email")
                                .getter(Customer::getEmail)
                                .setter(Customer::setEmail)
                                .tags(StaticAttributeTags.primarySortKey()))
                        .addAttribute(String.class, a -> a.name("name")
                                .getter(Customer::getCustName)
                                .setter(Customer::setCustName))
                        .addAttribute(Instant.class, a -> a.name("registrationDate")
                                .getter(Customer::getRegistrationDate)
                                .setter(Customer::setRegistrationDate))
                        .build();
```

# 创建增强型客户端和 `DynamoDbTable`
<a name="ddb-en-client-getting-started-dynamodbTable"></a>

## 创建增强型客户端
<a name="ddb-en-client-getting-started-dynamodbTable-eclient"></a>

该[DynamoDbEnhancedClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedClient.html)类或其异步类是使用 DynamoDB 增强型客户端 API 的入口点。[DynamoDbEnhancedAsyncClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedAsyncClient.html)

增强型客户端需要标准 `[DynamoDbClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/DynamoDbClient.html)` 才能执行工作。API 提供了两种创建 `DynamoDbEnhancedClient` 实例的方法。第一个选项是使用从配置设置中选取的默认设置创建标准 `DynamoDbClient`，如以下代码段所示。

```
DynamoDbEnhancedClient enhancedClient = DynamoDbEnhancedClient.create();
```

如果要配置底层标准客户端，可以将其提供给增强型客户端的生成器方法，如以下代码段所示。

```
// Configure an instance of the standard DynamoDbClient.
DynamoDbClient standardClient = DynamoDbClient.builder()
    .region(Region.US_EAST_1)
    .credentialsProvider(ProfileCredentialsProvider.create())
    .build();

// Use the configured standard client with the enhanced client.
DynamoDbEnhancedClient enhancedClient = DynamoDbEnhancedClient.builder()
    .dynamoDbClient(standardClient)
    .build();
```

## 创建 `DynamoDbTable` 实例
<a name="ddb-en-client-getting-started-dynamodbTable-table"></a>

可以将 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbTable.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbTable.html) 视为使用 `TableSchema` 提供的映射功能的 DynamoDB 表的客户端表示形式。该 `DynamoDbTable` 类提供了 CRUD 操作的方法，允许您与单个 DynamoDB 表进行交互。

`DynamoDbTable<T>` 是一个采用单一类型参数的通用类，无论是自定义类还是处理文档类型项目时的 `EnhancedDocument`。此参数类型在您使用的类和单个 DynamoDB 表之间建立关系。

使用 `DynamoDbEnhancedClient` 的 `table()` 工厂方法创建 `DynamoDbTable` 实例，如以下代码段所示。

```
static final DynamoDbTable<Customer> customerTable = 
        enhancedClient.table("Customer", TableSchema.fromBean(Customer.class));
```

`DynamoDbTable` 实例是单例类的候选实例，因为它们是不可变的，可以在整个应用程序中使用。

现在，您的代码具有可以使用 `Customer` 实例的 DynamoDB 表内存表示形式。实际的 DynamoDB 表可能存在，也可能不存在。如果名为 `Customer` 的表已经存在，则可以开始对其执行 CRUD 操作。如果该表不存在，请使用 `DynamoDbTable` 实例创建表，如下一部分所述。

# 如果需要，创建 DynamoDB 表
<a name="ddb-en-client-gs-ddbtable"></a>

创建 `DynamoDbTable` 实例后，使用它在 DynamoDB 中*一次性* 创建表。

## 创建表示例代码
<a name="ddb-en-client-gs-ddbtable-createex"></a>

以下示例基于 `Customer` 数据类创建一个 DynamoDB 表。

此示例创建了一个 DynamoDB 表，其名称为 `Customer`（与类名称相同，但表名称可以是其他名称）。不管您如何为表命名，都必须在其他应用程序中使用该名称才能使用该表。为了使用底层 DynamoDB 表，每次创建另一个 `DynamoDbTable` 对象时，都要为 `table()` 方法提供此名称。

传递给 `createTable` 方法的 Java lambda 参数 `builder` 允许您[自定义表](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/CreateTableEnhancedRequest.Builder.html)。在此示例中，配置了[预置吞吐量](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.ReadWriteCapacityMode.html#HowItWorks.ProvisionedThroughput.Manual)。要在创建表时使用默认设置，请跳过生成器，如以下代码段所示。

```
customerTable.createTable();
```

使用默认设置时，不会设置预置吞吐量的值。取而代之的是，表的计费模式将设置为[按需](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.ReadWriteCapacityMode.html#HowItWorks.OnDemand)。

该示例还在尝试打印出响应中收到的表名称之前使用了 `[DynamoDbWaiter](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/waiters/DynamoDbWaiter.html)`。创建表需要一些时间。因此，使用 waiter 意味着您不必编写在使用表之前轮询 DynamoDB 服务，以查看表是否存在的逻辑。

### 导入
<a name="ddb-en-client-gs-ddbtable-imports"></a>

```
import com.example.dynamodb.Customer;
import software.amazon.awssdk.core.internal.waiters.ResponseOrException;
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient;
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbTable;
import software.amazon.awssdk.enhanced.dynamodb.TableSchema;
import software.amazon.awssdk.enhanced.dynamodb.model.CreateTableEnhancedRequest;
import software.amazon.awssdk.services.dynamodb.model.DescribeTableResponse;
import software.amazon.awssdk.services.dynamodb.waiters.DynamoDbWaiter;
```

### 代码
<a name="ddb-en-client-gs-ddbtable-code"></a>

```
 public static void createCustomerTable(DynamoDbTable<Customer> customerTable, DynamoDbClient standardClient) {
     // Create the DynamoDB table using the 'customerTable' DynamoDbTable instance.
     customerTable.createTable(builder -> builder
             .provisionedThroughput(b -> b
                     .readCapacityUnits(10L)
                     .writeCapacityUnits(10L)
                     .build())
     );
     // The DynamoDbClient instance (named 'standardClient') passed to the builder for the DynamoDbWaiter is the same instance
     // that was passed to the builder of the DynamoDbEnhancedClient instance that we created previously.
     // By using the same instance, it ensures that the same Region that was configured on the standard DynamoDbClient 
     // instance is used for other service clients that accept a DynamoDbClient during construction.
     try (DynamoDbWaiter waiter = DynamoDbWaiter.builder().client(standardClient).build()) { // DynamoDbWaiter is Autocloseable
         ResponseOrException<DescribeTableResponse> response = waiter
                 .waitUntilTableExists(builder -> builder.tableName("Customer").build())
                 .matched();
         DescribeTableResponse tableDescription = response.response().orElseThrow(
                 () -> new RuntimeException("Customer table was not created."));
         // The actual error can be inspected in response.exception()
         logger.info("Customer table was created.");
     }
 }
```

**注意**  
从数据类生成表时，DynamoDB 表的属性名称以小写字母开头。如果您希望表的属性名称以大写字母开头，请使用 [`@DynamoDbAttribute(NAME)` 注释](ddb-en-client-adv-features-inex-attr.md)并提供所需的名称作为参数。

# 执行操作
<a name="ddb-en-client-gs-use"></a>

创建表后，请使用 `DynamoDbTable` 实例对 DynamoDB 表执行操作。

在以下示例中，将单例 `DynamoDbTable<Customer>` 作为参数与 [`Customer` 数据类](ddb-en-client-gs-tableschema.md#ddb-en-client-gs-tableschema-anno-bean-cust)实例一起传递，以向表中添加新项目。

```
    public static void putItemExample(DynamoDbTable<Customer> customerTable, Customer customer){
        logger.info(customer.toString());
        customerTable.putItem(customer);
    }
```

## `Customer` 对象
<a name="perform_ops_create_customer_instatnce"></a>

```
        Customer customer = new Customer();
        customer.setId("1");
        customer.setCustName("Customer Name");
        customer.setEmail("customer@example.com");
        customer.setRegistrationDate(Instant.parse("2023-07-03T10:15:30.00Z"));
```

在将 `customer` 对象发送到 DynamoDB 服务之前，请记录对象的 `toString()` 方法的输出，以将其与增强型客户端发送的内容进行比较。

```
Customer [id=1, name=Customer Name, email=customer@example.com, regDate=2023-07-03T10:15:30Z]
```

线级日志记录显示生成的请求的有效负载。增强型客户端从数据类生成了低级别表示形式。`regDate` 属性是 Java 中的一种 `Instant` 类型，以 DynamoDB 字符串的形式表示。

```
{
  "TableName": "Customer",
  "Item": {
    "registrationDate": {
      "S": "2023-07-03T10:15:30Z"
    },
    "id": {
      "S": "1"
    },
    "custName": {
      "S": "Customer Name"
    },
    "email": {
      "S": "customer@example.com"
    }
  }
}
```

# 使用现有表
<a name="ddb-en-client-gs-existingtable"></a>

上一部分介绍了如何从 Java 数据类开始，创建 DynamoDB 表。如果您已有表，并且想要使用增强型客户端的功能，则可以创建一个 Java 数据类来使用该表。您需要检查 DynamoDB 表并为数据类添加必要的注释。

在使用现有表之前，请调用 `DynamoDbEnhanced.table()` 方法。在前面的示例中，这是通过以下语句完成的。

```
DynamoDbTable<Customer> customerTable = enhancedClient.table("Customer", TableSchema.fromBean(Customer.class));
```

返回 `DynamoDbTable` 实例后，您可以立即开始使用底层表。您无需通过调用 `DynamoDbTable.createTable()` 方法来重新创建表。

以下示例通过立即从 DynamoDB 表中检索 `Customer` 实例，演示了这一点。

```
DynamoDbTable<Customer> customerTable = enhancedClient.table("Customer", TableSchema.fromBean(Customer.class));
// The Customer table exists already and has an item with a primary key value of "1" and a sort key value of "customer@example.com".
customerTable.getItem(
        Key.builder().
                partitionValue("1").
                sortValue("customer@example.com").build());
```

**重要**  
`table()` 方法中使用的表名称必须与现有 DynamoDB 表名称相匹配。

# 了解 DynamoDB 增强型客户端 API 的基础知识
<a name="ddb-en-client-use"></a>

本主题讨论 DynamoDB 增强型客户端 API 的基本功能，并将其与[标准 DynamoDB 客户端 API](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/package-summary.html) 进行了比较。

如果您不熟悉 DynamoDB 增强型客户端 API，建议您阅读[介绍性教程](ddb-en-client-getting-started.md)，熟悉基础类。

## Java 中的 DynamoDB 项目
<a name="ddb-en-client-use-usecase"></a>

DynamoDB 表用于存储项目。根据您的用例，Java 端的项目可以采用静态结构化数据形式或动态创建结构形式。

如果您的用例要求项目使用一组一致的属性，请使用[带注释的类](ddb-en-client-gs-tableschema.md#ddb-en-client-gs-tableschema-anno-bean)或使用[生成器](ddb-en-client-gs-tableschema.md#ddb-en-client-gs-tableschema-builder)生成相应的静态类型 `TableSchema`。

或者，如果您需要存储不同结构的项目，请创建一个 `DocumentTableSchema`。`DocumentTableSchema` 是[增强型文档 API](ddb-en-client-doc-api.md) 的一部分，只需要静态类型的主键，并且可以与 `EnhancedDocument` 实例结合使用来保存数据元素。增强型文档 API 将在另一个[主题](ddb-en-client-doc-api.md)中介绍。

## 数据模型类的属性类型
<a name="ddb-en-client-use-types"></a>

尽管与 Java 丰富的类型系统相比，DynamoDB 支持的[属性类型较少](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes)，但 DynamoDB 增强型客户端 API 提供了将 Java 类的成员与 DynamoDB 属性类型相互转换的机制。

Java 数据类的属性类型（属性）应该是对象类型，而不是基元。例如，始终使用 `Long` 和 `Integer` 对象数据类型，而不是 `long` 和 `int` 基元。

[默认情况下，DynamoDB 增强型客户端 API 支持大量类型的属性转换器，[例如](https://docs.oracle.com/javase/8/docs/api/java/lang/Integer.html)整数[[BigDecimal](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/internal/converter/attribute/BigDecimalAttributeConverter.html)、](https://docs.oracle.com/javase/8/docs/api/java/lang/String.html)字符串和即时。](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/internal/converter/attribute/InstantAsStringAttributeConverter.html)该列表显示在[ AttributeConverter 接口的已知实现类中](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/AttributeConverter.html)。该列表包括许多类型和集合，例如映射、列表和集。

要存储默认不支持或不符合约定的属性类型的数据，可以编写自定义`AttributeConverter`实现来进行转换。 JavaBean 有关[示例](ddb-en-client-adv-features-conversion.md#ddb-en-client-adv-features-conversion-example)，请参阅属性转换部分。

要存储属性类型的类符合 Java Bean 规范（或该类为[不可变数据类](ddb-en-client-use-immut.md)）的数据，可以采用两种方法。
+ 如果您有权访问源文件，则可以使用`@DynamoDbBean`（或`@DynamoDbImmutable`）为该类添加注释。讨论嵌套属性的部分提供了使用带注释的类的[示例](ddb-en-client-adv-features-nested.md#ddb-en-client-adv-features-nested-map-anno)。
+ 如果无权访问该属性的 JavaBean 数据类的源文件（或者您不想为自己有权访问的类的源文件添加注释），则可以使用生成器方法。这可在不定义键的情况下创建表架构。然后，您可以将此表架构嵌套在另一个表架构中以执行映射。嵌套属性部分提供了使用嵌套架构的[示例](ddb-en-client-adv-features-nested.md#ddb-en-client-adv-features-nested-map-builder)。

### Null 值
<a name="ddb-en-client-use-types-nulls"></a>

使用 `putItem` 方法时，增强型客户端不会在向 DynamoDB 发出的请求中包含映射数据对象的 null 值属性。

SDK 对 `updateItem` 请求的默认行为是从 DynamoDB 的项目中移除您在 `updateItem` 方法中提交的对象中设置为 null 的属性。如果您打算更新某些属性值并使其他属性值保持不变，则有两种选择。
+ 在更改值之前（使用 `getItem`）检索项目。通过使用这种方法，SDK 会将所有更新的值和旧值提交给 DynamoDB。
+ 在生成更新项目的请求时，使用 `[IgnoreNullsMode](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/IgnoreNullsMode.html).SCALAR_ONLY` 或 `IgnoreNullsMode.MAPS_ONLY`。两种模式都忽略对象中表示 DynamoDB 中标量属性的 null 值属性。本指南中的 [更新包含复杂类型的项目](ddb-en-client-adv-features-nested.md#ddb-en-client-adv-features-nested-updates) 主题包含有关 `IgnoreNullsMode` 值以及如何处理复杂类型的更多信息。

以下示例演示 `updateItem()` 方法的 `ignoreNullsMode()`。

```
    public static void updateItemNullsExample() {
        Customer customer = new Customer();
        customer.setCustName("CustomerName");
        customer.setEmail("email");
        customer.setId("1");
        customer.setRegistrationDate(Instant.now());

        logger.info("Original customer: {}", customer);

        // Put item with values for all attributes.
        try {
            customerAsyncDynamoDbTable.putItem(customer).join();
        } catch (RuntimeException rte) {
            logger.error("A exception occurred during putItem: {}", rte.getCause().getMessage(), rte);
            return;
        }

        // Create a Customer instance with the same 'id' and 'email' values, but a different 'name' value.
        // Do not set the 'registrationDate' attribute.
        Customer customerForUpdate = new Customer();
        customerForUpdate.setCustName("NewName");
        customerForUpdate.setEmail("email");
        customerForUpdate.setId("1");

        // Update item without setting the 'registrationDate' property and set IgnoreNullsMode to SCALAR_ONLY.
        try {
            Customer updatedWithNullsIgnored = customerAsyncDynamoDbTable.updateItem(b -> b
                            .item(customerForUpdate)
                            .ignoreNullsMode(IgnoreNullsMode.SCALAR_ONLY))
                    .join();
            logger.info("Customer updated with nulls ignored: {}", updatedWithNullsIgnored.toString());
        } catch (RuntimeException rte) {
            logger.error("An exception occurred during updateItem: {}", rte.getCause().getMessage(), rte);
            return;
        }

        // Update item without setting the registrationDate attribute and not setting ignoreNulls to true.
        try {
            Customer updatedWithNullsUsed = customerAsyncDynamoDbTable.updateItem(customerForUpdate)
                    .join();
            logger.info("Customer updated with nulls used: {}", updatedWithNullsUsed.toString());
        } catch (RuntimeException rte) {
            logger.error("An exception occurred during updateItem: {}", rte.getCause().getMessage(), rte);
        }
    }


// Logged lines. 
Original customer: Customer [id=1, name=CustomerName, email=email, regDate=2024-10-11T14:12:30.222858Z]
Customer updated with nulls ignored: Customer [id=1, name=NewName, email=email, regDate=2024-10-11T14:12:30.222858Z]
Customer updated with nulls used: Customer [id=1, name=NewName, email=email, regDate=null]
```

## DynamoDB 增强型客户端基本方法
<a name="ddb-en-client-use-basic-ops"></a>

增强型客户端的基本方法映射到它们以之命名的 DynamoDB 服务操作。以下示例演示每种方法的最简单变体。您可以通过传入增强型请求对象来自定义每种方法。增强型请求对象提供了标准 DynamoDB 客户端中可用的大部分功能。它们完整记录在《 AWS SDK for Java 2.x API Reference》中。

该示例使用了前面所示的 [`Customer` 类](ddb-en-client-gs-tableschema.md#ddb-en-client-gs-tableschema-anno-bean-cust)。

```
// CreateTable
customerTable.createTable();

// GetItem
Customer customer = customerTable.getItem(Key.builder().partitionValue("a123").build());

// UpdateItem
Customer updatedCustomer = customerTable.updateItem(customer);

// PutItem
customerTable.putItem(customer);

// DeleteItem
Customer deletedCustomer = customerTable.deleteItem(Key.builder().partitionValue("a123").sortValue(456).build());

// Query
PageIterable<Customer> customers = customerTable.query(keyEqualTo(k -> k.partitionValue("a123")));

// Scan
PageIterable<Customer> customers = customerTable.scan();

// BatchGetItem
BatchGetResultPageIterable batchResults = 
    enhancedClient.batchGetItem(r -> r.addReadBatch(ReadBatch.builder(Customer.class)
                                      .mappedTableResource(customerTable)
                                      .addGetItem(key1)
                                      .addGetItem(key2)
                                      .addGetItem(key3)
                                      .build()));

// BatchWriteItem
batchResults = enhancedClient.batchWriteItem(r -> r.addWriteBatch(WriteBatch.builder(Customer.class)
                                                   .mappedTableResource(customerTable)
                                                   .addPutItem(customer)
                                                   .addDeleteItem(key1)
                                                   .addDeleteItem(key1)
                                                   .build()));

// TransactGetItems
transactResults = enhancedClient.transactGetItems(r -> r.addGetItem(customerTable, key1)
                                                        .addGetItem(customerTable, key2));

// TransactWriteItems
enhancedClient.transactWriteItems(r -> r.addConditionCheck(customerTable, 
                                                           i -> i.key(orderKey)
                                                                 .conditionExpression(conditionExpression))
                                        .addUpdateItem(customerTable, customer)
                                        .addDeleteItem(customerTable, key));
```

## 比较 DynamoDB 增强型客户端与标准 DynamoDB 客户端
<a name="ddb-en-client-use-compare"></a>

[DynamoDB APIs 客户端（标准版[和](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/package-summary.html)增强版）都允许您使用 DynamoDB 表执行 CRUD（创建、读取、更新和删除）数据级操作。](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/package-summary.html)客户之间的区别在于 APIs 它是如何完成的。使用标准客户端，您可以直接处理低级别数据属性。增强型客户端 API 使用熟悉的 Java 类，并映射到后台的低级别 API。

虽然两个客户端都 APIs 支持数据级操作，但标准 DynamoDB 客户端也支持资源级操作。资源级操作管理数据库，例如创建备份、列出表和更新表。增强型客户端 API 支持一定数量的资源级操作，例如创建、描述和删除表。

为了说明两个客户端使用的不同方法 APIs，以下代码示例演示了如何使用标准客户端和增强型客户端创建同一个`ProductCatalog`表。

### 比较：使用标准 DynamoDB 客户端创建表
<a name="ddb-en-client-use-compare-cs1"></a>

```
DependencyFactory.dynamoDbClient().createTable(builder -> builder
        .tableName(TABLE_NAME)
        .attributeDefinitions(
                b -> b.attributeName("id").attributeType(ScalarAttributeType.N),
                b -> b.attributeName("title").attributeType(ScalarAttributeType.S),
                b -> b.attributeName("isbn").attributeType(ScalarAttributeType.S)
        )
        .keySchema(
                builder1 -> builder1.attributeName("id").keyType(KeyType.HASH),
                builder2 -> builder2.attributeName("title").keyType(KeyType.RANGE)
        )
        .globalSecondaryIndexes(builder3 -> builder3
                        .indexName("products_by_isbn")
                        .keySchema(builder2 -> builder2
                                .attributeName("isbn").keyType(KeyType.HASH))
                        .projection(builder2 -> builder2
                                .projectionType(ProjectionType.INCLUDE)
                                .nonKeyAttributes("price", "authors"))
                        .provisionedThroughput(builder4 -> builder4
                                .writeCapacityUnits(5L).readCapacityUnits(5L))
        )
        .provisionedThroughput(builder1 -> builder1
                .readCapacityUnits(5L).writeCapacityUnits(5L))
);
```

### 比较：使用 DynamoDB 增强型客户端创建表
<a name="ddb-en-client-use-compare-cs2"></a>

```
DynamoDbEnhancedClient enhancedClient = DependencyFactory.enhancedClient();
productCatalog = enhancedClient.table(TABLE_NAME, TableSchema.fromImmutableClass(ProductCatalog.class));
productCatalog.createTable(b -> b
        .provisionedThroughput(b1 -> b1.readCapacityUnits(5L).writeCapacityUnits(5L))
        .globalSecondaryIndices(b2 -> b2.indexName("products_by_isbn")
                .projection(b4 -> b4
                        .projectionType(ProjectionType.INCLUDE)
                        .nonKeyAttributes("price", "authors"))
                .provisionedThroughput(b3 -> b3.writeCapacityUnits(5L).readCapacityUnits(5L))
        )
);
```

增强型客户端使用以下带注释的数据类。DynamoDB 增强型客户端将 Java 数据类型映射到 DynamoDB 数据类型，代码不那么冗长，更易于理解。`ProductCatalog` 是在 DynamoDB 增强型客户端中使用不可变类的一个例子。本主题[后面将讨论](ddb-en-client-use-immut.md)为映射的数据类使用不可变类。

### `ProductCatalog` 类
<a name="ddb-en-client-use-compare-cs3"></a>

```
package org.example.tests.model;

import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbIgnore;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbImmutable;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSecondaryPartitionKey;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey;

import java.math.BigDecimal;
import java.util.Objects;
import java.util.Set;

@DynamoDbImmutable(builder = ProductCatalog.Builder.class)
public class ProductCatalog implements Comparable<ProductCatalog> {
    private Integer id;
    private String title;
    private String isbn;
    private Set<String> authors;
    private BigDecimal price;


    private ProductCatalog(Builder builder){
        this.authors = builder.authors;
        this.id = builder.id;
        this.isbn = builder.isbn;
        this.price = builder.price;
        this.title = builder.title;
    }

    public static Builder builder(){ return new Builder(); }

    @DynamoDbPartitionKey
    public Integer id() { return id; }
    
    @DynamoDbSortKey
    public String title() { return title; }
    
    @DynamoDbSecondaryPartitionKey(indexNames = "products_by_isbn")
    public String isbn() { return isbn; }
    public Set<String> authors() { return authors; }
    public BigDecimal price() { return price; }


    public static final class Builder {
      private Integer id;
      private String title;
      private String isbn;
      private Set<String> authors;
      private BigDecimal price;
      private Builder(){}

      public Builder id(Integer id) { this.id = id; return this; }
      public Builder title(String title) { this.title = title; return this; }
      public Builder isbn(String ISBN) { this.isbn = ISBN; return this; }
      public Builder authors(Set<String> authors) { this.authors = authors; return this; }
      public Builder price(BigDecimal price) { this.price = price; return this; }
      public ProductCatalog build() { return new ProductCatalog(this); }
  }

    @Override
    public String toString() {
        final StringBuffer sb = new StringBuffer("ProductCatalog{");
        sb.append("id=").append(id);
        sb.append(", title='").append(title).append('\'');
        sb.append(", isbn='").append(isbn).append('\'');
        sb.append(", authors=").append(authors);
        sb.append(", price=").append(price);
        sb.append('}');
        return sb.toString();
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        ProductCatalog that = (ProductCatalog) o;
        return id.equals(that.id) && title.equals(that.title) && Objects.equals(isbn, that.isbn) && Objects.equals(authors, that.authors) && Objects.equals(price, that.price);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, title, isbn, authors, price);
    }

    @Override
    @DynamoDbIgnore
    public int compareTo(ProductCatalog other) {
        if (this.id.compareTo(other.id) != 0){
            return this.id.compareTo(other.id);
        } else {
            return this.title.compareTo(other.title);
        }
    }
}
```

以下两个批量写入代码示例说明了使用标准客户端时，相较于增强型客户端，代码的冗长和缺乏类型安全性。

### 比较：使用标准 DynamoDB 客户端的批量写入操作
<a name="ddb-en-client-use-compare-cs4"></a>

```
    public static void batchWriteStandard(DynamoDbClient dynamoDbClient, String tableName) {

        Map<String, AttributeValue> catalogItem = Map.of(
                "authors", AttributeValue.builder().ss("a", "b").build(),
                "id", AttributeValue.builder().n("1").build(),
                "isbn", AttributeValue.builder().s("1-565-85698").build(),
                "title", AttributeValue.builder().s("Title 1").build(),
                "price", AttributeValue.builder().n("52.13").build());

        Map<String, AttributeValue> catalogItem2 = Map.of(
                "authors", AttributeValue.builder().ss("a", "b", "c").build(),
                "id", AttributeValue.builder().n("2").build(),
                "isbn", AttributeValue.builder().s("1-208-98073").build(),
                "title", AttributeValue.builder().s("Title 2").build(),
                "price", AttributeValue.builder().n("21.99").build());

        Map<String, AttributeValue> catalogItem3 = Map.of(
                "authors", AttributeValue.builder().ss("g", "k", "c").build(),
                "id", AttributeValue.builder().n("3").build(),
                "isbn", AttributeValue.builder().s("7-236-98618").build(),
                "title", AttributeValue.builder().s("Title 3").build(),
                "price", AttributeValue.builder().n("42.00").build());

        Set<WriteRequest> writeRequests = Set.of(
                WriteRequest.builder().putRequest(b -> b.item(catalogItem)).build(),
                WriteRequest.builder().putRequest(b -> b.item(catalogItem2)).build(),
                WriteRequest.builder().putRequest(b -> b.item(catalogItem3)).build());

        Map<String, Set<WriteRequest>> productCatalogItems = Map.of(
                "ProductCatalog", writeRequests);

        BatchWriteItemResponse response = dynamoDbClient.batchWriteItem(b -> b.requestItems(productCatalogItems));

        logger.info("Unprocessed items: " + response.unprocessedItems().size());
    }
```

### 比较：使用 DynamoDB 增强型客户端的批量写入操作
<a name="ddb-en-client-use-compare-cs5"></a>

```
    public static void batchWriteEnhanced(DynamoDbTable<ProductCatalog> productCatalog) {
        ProductCatalog prod = ProductCatalog.builder()
                .id(1)
                .isbn("1-565-85698")
                .authors(new HashSet<>(Arrays.asList("a", "b")))
                .price(BigDecimal.valueOf(52.13))
                .title("Title 1")
                .build();
        ProductCatalog prod2 = ProductCatalog.builder()
                .id(2)
                .isbn("1-208-98073")
                .authors(new HashSet<>(Arrays.asList("a", "b", "c")))
                .price(BigDecimal.valueOf(21.99))
                .title("Title 2")
                .build();
        ProductCatalog prod3 = ProductCatalog.builder()
                .id(3)
                .isbn("7-236-98618")
                .authors(new HashSet<>(Arrays.asList("g", "k", "c")))
                .price(BigDecimal.valueOf(42.00))
                .title("Title 3")
                .build();

        BatchWriteResult batchWriteResult = DependencyFactory.enhancedClient()
                .batchWriteItem(b -> b.writeBatches(
                        WriteBatch.builder(ProductCatalog.class)
                                .mappedTableResource(productCatalog)
                                .addPutItem(prod).addPutItem(prod2).addPutItem(prod3)
                                .build()
                ));
        logger.info("Unprocessed items: " + batchWriteResult.unprocessedPutItemsForTable(productCatalog).size());
    }
```

# 使用不可变数据类
<a name="ddb-en-client-use-immut"></a>

DynamoDB 增强型客户端 API 的映射功能适用于不可变的数据类。不可变类只有 getter，且需要一个生成器类，使 SDK 可用来创建该类的实例。不可变类不像 [Customer 类](ddb-en-client-gs-tableschema.md#ddb-en-client-gs-tableschema-anno-bean-cust)那样使用 `@DynamoDbBean` 注释，而是使用 `@DynamoDbImmutable` 注释，该注释采用一个指示要使用的生成器类的参数。

以下类是 `Customer` 的不可变版本。

```
package org.example.tests.model.immutable;

import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbImmutable;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSecondaryPartitionKey;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSecondarySortKey;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey;

import java.time.Instant;

@DynamoDbImmutable(builder = CustomerImmutable.Builder.class)
public class CustomerImmutable {
    private final String id;
    private final String name;
    private final String email;
    private final Instant regDate;

    private CustomerImmutable(Builder b) {
        this.id = b.id;
        this.email = b.email;
        this.name = b.name;
        this.regDate = b.regDate;
    }

    // This method will be automatically discovered and used by the TableSchema.
    public static Builder builder() { return new Builder(); }

    @DynamoDbPartitionKey
    public String id() { return this.id; }

    @DynamoDbSortKey
    public String email() { return this.email; }

    @DynamoDbSecondaryPartitionKey(indexNames = "customers_by_name")
    public String name() { return this.name; }

    @DynamoDbSecondarySortKey(indexNames = {"customers_by_date", "customers_by_name"})
    public Instant regDate() { return this.regDate; }

    public static final class Builder {
        private String id;
        private String email;
        private String name;
        private Instant regDate;

        // The private Builder constructor is visible to the enclosing CustomerImmutable class.
        private Builder() {}

        public Builder id(String id) { this.id = id; return this; }
        public Builder email(String email) { this.email = email; return this; }
        public Builder name(String name) { this.name = name; return this; }
        public Builder regDate(Instant regDate) { this.regDate = regDate; return this; }

        // This method will be automatically discovered and used by the TableSchema.
        public CustomerImmutable build() { return new CustomerImmutable(this); }
    }
}
```

使用 `@DynamoDbImmutable` 注释数据类时，您必须满足以下要求。

1. 每个既不覆盖 `Object.class` 又未使用 `@DynamoDbIgnore` 注释的方法都必须是 DynamoDB 表属性的 getter。

1. 每个 getter 在生成器类上都必须有一个对应的区分大小写的 setter。

1. 必须仅满足以下任一构造条件。
   + 生成器类必须具有公共默认构造函数。
   + 数据类必须有一个名为 `builder()` 的公共静态方法，该方法不带任何参数并返回生成器类的实例。此选项显示在不可变 `Customer` 类中。

1.  生成器类必须有一个名为 `build()` 的公共方法，该方法不带任何参数并返回不可变类的实例。

要为不可变类创建 `TableSchema`，请使用 `TableSchema` 上的 `fromImmutableClass()` 方法，如以下代码段所示。

```
static final TableSchema<CustomerImmutable> customerImmutableTableSchema = 
                         TableSchema.fromImmutableClass(CustomerImmutable.class);
```

就像您可以从可变类创建 DynamoDB 表一样，您也可以通过*一次性* 调用 `DynamoDbTable` 的 `createTable()` 从不可变类创建该表，如以下代码段示例所示。

```
static void createTableFromImmutable(DynamoDbEnhancedClient enhancedClient, String tableName, DynamoDbWaiter waiter){
    // First, create an in-memory representation of the table using the 'table()' method of the DynamoDb Enhanced Client.
    // 'table()' accepts a name for the table and a TableSchema instance that you created previously.
    DynamoDbTable<CustomerImmutable> customerDynamoDbTable = enhancedClient
            .table(tableName, TableSchema.fromImmutableClass(CustomerImmutable.class));
        
    // Second, call the 'createTable()' method on the DynamoDbTable instance.
    customerDynamoDbTable.createTable();
    waiter.waitUntilTableExists(b -> b.tableName(tableName));
}
```

## 使用第三方库，例如 Lombok
<a name="ddb-en-client-use-immut-lombok"></a>

第三方库（例如 [Project Lombok](https://projectlombok.org/)）可帮助生成与不可变对象关联的样板代码。只要数据类遵循本部分详述的约定，DynamoDB 增强型客户端 API 就可以与这些库结合使用。

以下示例演示带有 Lombok 注释的不可变 `CustomerImmutable` 类。请注意 Lombok 的 `onMethod` 功能是如何将基于属性的 DynamoDB 注释（例如 `@DynamoDbPartitionKey`）复制到生成的代码中的。

```
@Value
@Builder
@DynamoDbImmutable(builder = Customer.CustomerBuilder.class)
public class Customer {
    @Getter(onMethod_=@DynamoDbPartitionKey)
    private String id;

    @Getter(onMethod_=@DynamoDbSortKey)
    private String email;

    @Getter(onMethod_=@DynamoDbSecondaryPartitionKey(indexNames = "customers_by_name"))
    private String name;

    @Getter(onMethod_=@DynamoDbSecondarySortKey(indexNames = {"customers_by_date", "customers_by_name"}))
    private Instant createdDate;
}
```

# 使用表达式和条件
<a name="ddb-en-client-expressions"></a>

DynamoDB 增强型客户端 API 中的表达式是 [DynamoDB 表达式](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.html)的 Java 表示形式。

DynamoDB 增强型客户端 API 使用三种类型的表达式：

[Expression](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/Expression.html)  
`Expression` 类在定义条件和筛选条件时使用。

[https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/QueryConditional.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/QueryConditional.html)  
这种类型的表达式表示查询操作的[关键条件](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Query.html#Query.KeyConditionExpressions)。

[https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/update/UpdateExpression.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/update/UpdateExpression.html)  
该类可帮助您编写 DynamoDB [更新](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html)表达式，当您更新项目时，目前会在扩展框架中使用该类。

## 表达式刨析
<a name="ddb-en-client-expressions-compoonents"></a>

表达式由以下内容组成：
+ 字符串表达式（必需）。该字符串包含一个 DynamoDB 逻辑表达式，其中包含属性名称和属性值的占位符名称。
+ 表达式值映射（通常是必需的）。
+ 表达式名称映射（可选）。

使用生成器生成一个采用以下一般形式的 `Expression` 对象。

```
Expression expression = Expression.builder()
                            .expression(<String>)
                            .expressionNames(<Map>)
                            .expressionValues(<Map>)
                           .build()
```

`Expression` 通常需要表达式值映射。映射提供了字符串表达式中占位符的值。地图键由前面带有冒号 (`:`) 的占位符名称组成，地图值是的实例。[AttributeValue](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/AttributeValue.html)该[AttributeValues](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/internal/AttributeValues.html)类具有从字面量生成`AttributeValue`实例的便捷方法。或者，您可以使用 `AttributeValue.Builder` 生成 `AttributeValue` 实例。

以下代码段展示了在注释行 2 之后有两个条目的映射。传递给 `expression()` 方法的字符串（显示在注释行 1 之后）包含 DynamoDB 在执行操作之前解析的占位符。此代码段不包含表达式名称映射，因为 *price* 是允许的属性名称。

```
    public static void scanAsync(DynamoDbAsyncTable productCatalog) {
        ScanEnhancedRequest request = ScanEnhancedRequest.builder()
                .consistentRead(true)
                .attributesToProject("id", "title", "authors", "price")
                .filterExpression(Expression.builder()
                        // 1. :min_value and :max_value are placeholders for the values provided by the map
                        .expression("price >= :min_value AND price <= :max_value")
                        // 2. Two values are needed for the expression and each is supplied as a map entry.
                        .expressionValues(
                                Map.of( ":min_value", numberValue(8.00),
                                        ":max_value", numberValue(400_000.00)))
                        .build())
                .build();
```

如果 DynamoDB 表中的属性名称是保留字、以数字开头或包含空格，则 `Expression` 需要表达式名称映射。

例如，如果属性名称是 `1price`，而不是前面的代码示例中的 `price`，则需要修改示例，如以下示例所示。

```
        ScanEnhancedRequest request = ScanEnhancedRequest.builder()
                .filterExpression(Expression.builder()
                        .expression("#price >= :min_value AND #price <= :max_value")
                        .expressionNames( Map.of("#price", "1price") )
                        .expressionValues(
                                Map.of(":min_value", numberValue(8.00),
                                        ":max_value", numberValue(400_000.00)))
                        .build())
                .build();
```

表达式名称的占位符以井号 (`#`) 开头。表达式名称映射的条目使用占位符作为键，使用属性名称作为值。使用 `expressionNames()` 方法将映射添加到表达式生成器中。DynamoDB 会在执行操作之前解析属性名称。

如果在字符串表达式中使用函数，则不需要表达式值。表达式函数的一个例子是 `attribute_exists(<attribute_name>)`。

以下示例构建了一个使用 [DynamoDB 函数](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html#Expressions.OperatorsAndFunctions.Functions)的 `Expression`。本示例中的表达式字符串不使用占位符。此表达式可用于 `putItem` 操作以检查数据库中是否已存在 `movie` 属性值等于数据对象的 `movie` 属性的项目。

```
Expression exp = Expression.builder().expression("attribute_not_exists (movie)").build();
```

《DynamoDB 开发人员指南》包含有关可用于 DynamoDB 的[低级别表达式](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.html)的完整信息。

## 条件表达式和条件语句
<a name="ddb-en-client-expressions-cond"></a>

使用 `putItem()`、`updateItem()` 和 `deleteItem()` 方法时，以及使用事务和批处理操作时，您可以使用 `[Expression](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/Expression.html)` 对象来指定 DynamoDB 必须满足什么条件才能继续执行操作。这些表达式是命名条件表达式。有关示例，请参阅本指南中显示的[事务示例](ddb-en-client-use-multiop-trans.md#ddb-en-client-use-multiop-trans-writeitems-opcondition)的 `addDeleteItem()` 方法中使用的条件表达式（在注释行 1 之后）。

使用 `query()` 方法时，条件表示为 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/QueryConditional.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/QueryConditional.html)。`QueryConditional` 类有几种静态便利方法，可帮助您编写用于确定要从 DynamoDB 读取哪些项目的标准。

有关 `QueryConditionals` 的示例，请参阅本指南[`Query` 方法示例](ddb-en-client-use-multirecord.md#ddb-en-client-use-multirecord-query-example)部分的第一个代码示例。

## 筛选条件表达式
<a name="ddb-en-client-expressions-filter"></a>

筛选表达式用于扫描和查询操作以筛选返回的项目。

从数据库中读取所有数据后，会应用筛选表达式，因此读取成本与不使用筛选条件时的读取成本相同。《Amazon DynamoDB 开发人员指南》**提供了有关使用筛选表达式进行[查询](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Query.html#Query.FilterExpression)和[扫描](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Scan.html#Scan.FilterExpression)操作的更多信息。

以下示例展示了添加到扫描请求的筛选表达式。该标准将返回的项目限制为价格介于 8.00 到 80.00（含）之间的项目。

```
        Map<String, AttributeValue> expressionValues = Map.of(
                ":min_value", numberValue(8.00),
                ":max_value", numberValue(80.00));

        ScanEnhancedRequest request = ScanEnhancedRequest.builder()
                .consistentRead(true)
                // 1. the 'attributesToProject()' method allows you to specify which values you want returned.
                .attributesToProject("id", "title", "authors", "price")
                // 2. Filter expression limits the items returned that match the provided criteria.
                .filterExpression(Expression.builder()
                        .expression("price >= :min_value AND price <= :max_value")
                        .expressionValues(expressionValues)
                        .build())
                .build();
```

## 更新表达式
<a name="ddb-en-client-expressions-update"></a>

DynamoDB 增强型客户端的 `updateItem()` 方法提供了一种在 DynamoDB 中更新项目的标准方法。[但是，当您需要更多功能时，请[UpdateExpressions](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/update/UpdateExpression.html)提供 DynamoDB 更新表达式语法的类型安全表示形式。](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html)例如，您可以使用 `UpdateExpressions` 增加值而不必先读取 DynamoDB 中的项目，或者将单个成员添加到列表中。更新表达式目前在 `updateItem()` 方法的自定义扩展中可用。

有关使用更新表达式的示例，请参阅本指南中的[自定义扩展示例](ddb-en-client-extensions-custom.md)。

有关更新表达式的更多信息，请参阅《[Amazon DynamoDB 开发人员指南](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html)》。

# 处理分页结果：扫描和查询
<a name="ddb-en-client-use-multirecord"></a>

DynamoDB 增强型客户端 API 的 `scan`、`query` 和 `batch` 方法返回包含一个或多个*页面* 的响应。一个页面包含一个或多个项目。您的代码可以按页处理响应，也可以处理单个项目。

同步`DynamoDbEnhancedClient`客户端返回的分页响应返回一个[PageIterable](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/PageIterable.html)对象，而异步客户端返回的响应`DynamoDbEnhancedAsyncClient`返回一个[PagePublisher](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/PagePublisher.html)对象。

本节介绍如何处理分页结果，并提供使用扫描和查询 APIs的示例。

## 扫描表
<a name="ddb-en-client-use-multirecord-scan"></a>

SDK 的 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbAsyncTable.html#scan(java.util.function.Consumer)](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbAsyncTable.html#scan(java.util.function.Consumer)) 方法对应于同名的 [DynamoDB 操作](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html)。DynamoDB 增强型客户端 API 提供了相同的选项，但它使用熟悉的对象模型并为您处理分页。

首先，我们通过查看同步映射类的`scan`方法来探索`PageIterable`接口[DynamoDbTable](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbTable.html)。

### 使用同步 API
<a name="ddb-en-client-use-multirecord-scan-sync"></a>

以下示例演示使用[表达式](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/Expression.html)筛选返回项的 `scan` 方法。[ProductCatalog](ddb-en-client-use.md#ddb-en-client-use-compare-cs3)是前面显示的模型对象。

在评论行 2 之后显示的筛选表达式将退回的`ProductCatalog`商品限制为价格在 8.00 到 80.00 之间（含）的商品。

此示例还使用注释行 1 后面显示的`attributesToProject`方法排除这些`isbn`值。

在注释行 3 之后，`scan` 方法返回 `PageIterable` 对象 `pagedResults`。`PageIterable` 的 `stream` 方法返回一个 [https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html) 对象，您可以使用该对象来处理页面。在此示例中，计算并记录了页数。

从注释行 4 开始，该示例演示访问 `ProductCatalog` 项目的两种变体。注释行 4a 之后的版本通过每个页面进行流式传输，并对每页上的项目进行排序和记录。注释行 4b 之后的版本会跳过页面迭代并直接访问项目。

由于 `PageIterable` 接口有两个父接口（[https://docs.oracle.com/javase/8/docs/api/java/lang/Iterable.html](https://docs.oracle.com/javase/8/docs/api/java/lang/Iterable.html) 和 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/pagination/sync/SdkIterable.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/pagination/sync/SdkIterable.html)），因此提供了多种方法来处理结果。`Iterable` 引入了 `forEach`、`iterator` 和 `spliterator` 方法，而 `SdkIterable` 引入了 `stream` 方法。

```
    public static void scanSync(DynamoDbTable<ProductCatalog> productCatalog) {

        Map<String, AttributeValue> expressionValues = Map.of(
                ":min_value", numberValue(8.00),
                ":max_value", numberValue(80.00));

        ScanEnhancedRequest request = ScanEnhancedRequest.builder()
                .consistentRead(true)
                // 1. the 'attributesToProject()' method allows you to specify which values you want returned.
                .attributesToProject("id", "title", "authors", "price")
                // 2. Filter expression limits the items returned that match the provided criteria.
                .filterExpression(Expression.builder()
                        .expression("price >= :min_value AND price <= :max_value")
                        .expressionValues(expressionValues)
                        .build())
                .build();

        // 3. A PageIterable object is returned by the scan method.
        PageIterable<ProductCatalog> pagedResults = productCatalog.scan(request);
        logger.info("page count: {}", pagedResults.stream().count());

        // 4. Log the returned ProductCatalog items using two variations.
        // 4a. This version sorts and logs the items of each page.
        pagedResults.stream().forEach(p -> p.items().stream()
                .sorted(Comparator.comparing(ProductCatalog::price))
                .forEach(
                        item -> logger.info(item.toString())
                ));
        // 4b. This version sorts and logs all items for all pages.
        pagedResults.items().stream()
                .sorted(Comparator.comparing(ProductCatalog::price))
                .forEach(
                        item -> logger.info(item.toString())
                );
    }
```

### 使用异步 API
<a name="ddb-en-client-use-multirecord-scan-async"></a>

异步 `scan` 方法将结果作为 `PagePublisher` 对象返回。`PagePublisher` 接口有两种 `subscribe` 方法可用于处理响应页面。一种 `subscribe` 方法来自 `org.reactivestreams.Publisher` 父接口。要使用第一个选项处理页面，请向 `subscribe` 方法传递一个 `[Subscriber](https://www.reactive-streams.org/reactive-streams-1.0.0-javadoc/org/reactivestreams/Subscriber.html)` 实例。接下来的第一个示例演示了 `subscribe` 方法的用法。

第二种`subscribe`方法来自接[SdkPublisher](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/async/SdkPublisher.html)口。此版本的 `subscribe` 接受 [https://docs.oracle.com/javase/8/docs/api/java/util/function/Consumer.html](https://docs.oracle.com/javase/8/docs/api/java/util/function/Consumer.html) 而不是 `Subscriber`。此 `subscribe` 方法变体如接下来的第二个示例所示。

以下示例演示 `scan` 方法的异步版本，该版本使用了上一个示例中的相同筛选表达式。

在注释行 3 之后，`DynamoDbAsyncTable.scan` 返回一个 `PagePublisher` 对象。在下一行，代码创建一个 `org.reactivestreams.Subscriber` 接口实例 `ProductCatalogSubscriber`，在注释行 4 之后，该实例订阅到 `PagePublisher`。

在 `ProductCatalogSubscriber` 类示例的注释行 8 之后，`Subscriber` 对象从 `onNext` 方法中的每个页面收集 `ProductCatalog` 项目。这些项目存储在私有 `List` 变量中，并在调用代码中使用 `ProductCatalogSubscriber.getSubscribedItems()` 方法对它们进行访问。这是在注释行 5 之后调用的。

检索了列表后，代码按价格对所有 `ProductCatalog` 项目进行排序并记录每个项目。

`ProductCatalogSubscriber`类[CountDownLatch](https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/CountDownLatch.html)中的会阻塞调用线程，直到所有项目都已添加到列表中，然后在注释行 5 之后继续。

```
    public static void scanAsync(DynamoDbAsyncTable productCatalog) {
        ScanEnhancedRequest request = ScanEnhancedRequest.builder()
                .consistentRead(true)
                .attributesToProject("id", "title", "authors", "price")
                .filterExpression(Expression.builder()
                        // 1. :min_value and :max_value are placeholders for the values provided by the map
                        .expression("price >= :min_value AND price <= :max_value")
                        // 2. Two values are needed for the expression and each is supplied as a map entry.
                        .expressionValues(
                                Map.of( ":min_value", numberValue(8.00),
                                        ":max_value", numberValue(400_000.00)))
                        .build())
                .build();

        // 3. A PagePublisher object is returned by the scan method.
        PagePublisher<ProductCatalog> pagePublisher = productCatalog.scan(request);
        ProductCatalogSubscriber subscriber = new ProductCatalogSubscriber();
        // 4. Subscribe the ProductCatalogSubscriber to the PagePublisher.
        pagePublisher.subscribe(subscriber);
        // 5. Retrieve all collected ProductCatalog items accumulated by the subscriber.
        subscriber.getSubscribedItems().stream()
                .sorted(Comparator.comparing(ProductCatalog::price))
                .forEach(item ->
                        logger.info(item.toString()));
        // 6. Use a Consumer to work through each page.
        pagePublisher.subscribe(page -> page
                        .items().stream()
                        .sorted(Comparator.comparing(ProductCatalog::price))
                        .forEach(item ->
                                logger.info(item.toString())))
                .join(); // If needed, blocks the subscribe() method thread until it is finished processing.
        // 7. Use a Consumer to work through each ProductCatalog item.
        pagePublisher.items()
                .subscribe(product -> logger.info(product.toString()))
                .exceptionally(failure -> {
                    logger.error("ERROR  - ", failure);
                    return null;
                })
                .join(); // If needed, blocks the subscribe() method thread until it is finished processing.
    }
```

```
    private static class ProductCatalogSubscriber implements Subscriber<Page<ProductCatalog>> {
        private CountDownLatch latch = new CountDownLatch(1);
        private Subscription subscription;
        private List<ProductCatalog> itemsFromAllPages = new ArrayList<>();

        @Override
        public void onSubscribe(Subscription sub) {
            subscription = sub;
            subscription.request(1L);
            try {
                latch.await(); // Called by main thread blocking it until latch is released.
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }

        @Override
        public void onNext(Page<ProductCatalog> productCatalogPage) {
            // 8. Collect all the ProductCatalog instances in the page, then ask the publisher for one more page.
            itemsFromAllPages.addAll(productCatalogPage.items());
            subscription.request(1L);
        }

        @Override
        public void onError(Throwable throwable) {
        }

        @Override
        public void onComplete() {
            latch.countDown(); // Call by subscription thread; latch releases.
        }

        List<ProductCatalog> getSubscribedItems() {
            return this.itemsFromAllPages;
        }
    }
```

以下代码段示例使用的 `PagePublisher.subscribe` 方法版本在注释行 6 之后接受 `Consumer`。Java lambda 参数使用页面，进一步处理每个项目。在此示例中，对每个页面进行处理，并对每页上的项目进行排序和记录。

```
        // 6. Use a Consumer to work through each page.
        pagePublisher.subscribe(page -> page
                        .items().stream()
                        .sorted(Comparator.comparing(ProductCatalog::price))
                        .forEach(item ->
                                logger.info(item.toString())))
                .join(); // If needed, blocks the subscribe() method thread until it is finished processing.
```

`PagePublisher` 的 `items` 方法对模型实例进行解包，以便您的代码可以直接处理这些项目。以下代码段演示了这种方法。

```
        // 7. Use a Consumer to work through each ProductCatalog item.
        pagePublisher.items()
                .subscribe(product -> logger.info(product.toString()))
                .exceptionally(failure -> {
                    logger.error("ERROR  - ", failure);
                    return null;
                })
                .join(); // If needed, blocks the subscribe() method thread until it is finished processing.
```

## 查询表
<a name="ddb-en-client-use-multirecord-query"></a>

您可以使用 DynamoDB 增强型客户端来查询您的表并检索符合特定条件的多个项目。[https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbTable.html#query(software.amazon.awssdk.enhanced.dynamodb.model.QueryEnhancedRequest)](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbTable.html#query(software.amazon.awssdk.enhanced.dynamodb.model.QueryEnhancedRequest)) 方法使用在数据类中定义的 `@DynamoDbPartitionKey` 和可选 `@DynamoDbSortKey` 注释，根据主键值来查找项目。

`query()` 方法需要分区键值，并且可以选择接受排序键条件以进一步优化结果。与 `scan` API 一样，对于同步调用，查询会返回 `PageIterable`，对于异步调用，则返回 `PagePublisher`。

### `Query` 方法示例
<a name="ddb-en-client-use-multirecord-query-example"></a>

下面的 `query()` 方法代码示例使用 `MovieActor` 类。数据类定义了一个复合主键，该主键由作为分区键的 **`movie`** 属性和作为排序键的 **`actor`** 属性组成。

#### `MovieActor` 类
<a name="ddb-en-client-use-movieactor-class"></a>

```
package org.example.tests.model;

import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbAttribute;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSecondaryPartitionKey;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSecondarySortKey;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey;

import java.util.Objects;

@DynamoDbBean
public class MovieActor implements Comparable<MovieActor> {

    private String movieName;
    private String actorName;
    private String actingAward;
    private Integer actingYear;
    private String actingSchoolName;

    @DynamoDbPartitionKey
    @DynamoDbAttribute("movie")
    public String getMovieName() {
        return movieName;
    }

    public void setMovieName(String movieName) {
        this.movieName = movieName;
    }

    @DynamoDbSortKey
    @DynamoDbAttribute("actor")
    public String getActorName() {
        return actorName;
    }

    public void setActorName(String actorName) {
        this.actorName = actorName;
    }

    @DynamoDbSecondaryPartitionKey(indexNames = "acting_award_year")
    @DynamoDbAttribute("actingaward")
    public String getActingAward() {
        return actingAward;
    }

    public void setActingAward(String actingAward) {
        this.actingAward = actingAward;
    }

    @DynamoDbSecondarySortKey(indexNames = {"acting_award_year", "movie_year"})
    @DynamoDbAttribute("actingyear")
    public Integer getActingYear() {
        return actingYear;
    }

    public void setActingYear(Integer actingYear) {
        this.actingYear = actingYear;
    }

    @DynamoDbAttribute("actingschoolname")
    public String getActingSchoolName() {
        return actingSchoolName;
    }

    public void setActingSchoolName(String actingSchoolName) {
        this.actingSchoolName = actingSchoolName;
    }

    @Override
    public String toString() {
        final StringBuffer sb = new StringBuffer("MovieActor{");
        sb.append("movieName='").append(movieName).append('\'');
        sb.append(", actorName='").append(actorName).append('\'');
        sb.append(", actingAward='").append(actingAward).append('\'');
        sb.append(", actingYear=").append(actingYear);
        sb.append(", actingSchoolName='").append(actingSchoolName).append('\'');
        sb.append('}');
        return sb.toString();
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        MovieActor that = (MovieActor) o;
        return Objects.equals(movieName, that.movieName) && Objects.equals(actorName, that.actorName) && Objects.equals(actingAward, that.actingAward) && Objects.equals(actingYear, that.actingYear) && Objects.equals(actingSchoolName, that.actingSchoolName);
    }

    @Override
    public int hashCode() {
        return Objects.hash(movieName, actorName, actingAward, actingYear, actingSchoolName);
    }

    @Override
    public int compareTo(MovieActor o) {
        if (this.movieName.compareTo(o.movieName) != 0){
            return this.movieName.compareTo(o.movieName);
        } else {
            return this.actorName.compareTo(o.actorName);
        }
    }
}
```

对以下项目进行查询之后的代码示例。

#### `MovieActor` 表中的项目
<a name="ddb-en-client-use-movieactor-items"></a>

```
MovieActor{movieName='movie01', actorName='actor0', actingAward='actingaward0', actingYear=2001, actingSchoolName='null'}
MovieActor{movieName='movie01', actorName='actor1', actingAward='actingaward1', actingYear=2001, actingSchoolName='actingschool1'}
MovieActor{movieName='movie01', actorName='actor2', actingAward='actingaward2', actingYear=2001, actingSchoolName='actingschool2'}
MovieActor{movieName='movie01', actorName='actor3', actingAward='actingaward3', actingYear=2001, actingSchoolName='null'}
MovieActor{movieName='movie01', actorName='actor4', actingAward='actingaward4', actingYear=2001, actingSchoolName='actingschool4'}
MovieActor{movieName='movie02', actorName='actor0', actingAward='actingaward0', actingYear=2002, actingSchoolName='null'}
MovieActor{movieName='movie02', actorName='actor1', actingAward='actingaward1', actingYear=2002, actingSchoolName='actingschool1'}
MovieActor{movieName='movie02', actorName='actor2', actingAward='actingaward2', actingYear=2002, actingSchoolName='actingschool2'}
MovieActor{movieName='movie02', actorName='actor3', actingAward='actingaward3', actingYear=2002, actingSchoolName='null'}
MovieActor{movieName='movie02', actorName='actor4', actingAward='actingaward4', actingYear=2002, actingSchoolName='actingschool4'}
MovieActor{movieName='movie03', actorName='actor0', actingAward='actingaward0', actingYear=2003, actingSchoolName='null'}
MovieActor{movieName='movie03', actorName='actor1', actingAward='actingaward1', actingYear=2003, actingSchoolName='actingschool1'}
MovieActor{movieName='movie03', actorName='actor2', actingAward='actingaward2', actingYear=2003, actingSchoolName='actingschool2'}
MovieActor{movieName='movie03', actorName='actor3', actingAward='actingaward3', actingYear=2003, actingSchoolName='null'}
MovieActor{movieName='movie03', actorName='actor4', actingAward='actingaward4', actingYear=2003, actingSchoolName='actingschool4'}
```

以下代码定义了两个 `QueryConditional` 实例：`keyEqual`（在注释行 1 之后）和 `sortGreaterThanOrEqualTo`（在注释行 1a 之后）。

#### 按分区键查询项目
<a name="keyEqual-query-conditional-example"></a>

`keyEqual` 实例匹配分区键值为 **`movie01`** 的项目。

此示例还在注释行 2 之后定义了一个筛选表达式，用于筛选掉任何没有 **`actingschoolname`** 值的项目。

`QueryEnhancedRequest` 组合了查询的键条件和筛选表达式。

```
    public static void query(DynamoDbTable movieActorTable) {

        // 1. Define a QueryConditional instance to return items matching a partition value.
        QueryConditional keyEqual = QueryConditional.keyEqualTo(b -> b.partitionValue("movie01"));
        // 1a. Define a QueryConditional that adds a sort key criteria to the partition value criteria.
        QueryConditional sortGreaterThanOrEqualTo = QueryConditional.sortGreaterThanOrEqualTo(b -> b.partitionValue("movie01").sortValue("actor2"));
        // 2. Define a filter expression that filters out items whose attribute value is null.
        final Expression filterOutNoActingschoolname = Expression.builder().expression("attribute_exists(actingschoolname)").build();

        // 3. Build the query request.
        QueryEnhancedRequest tableQuery = QueryEnhancedRequest.builder()
                .queryConditional(keyEqual)
                .filterExpression(filterOutNoActingschoolname)
                .build();
        // 4. Perform the query using the "keyEqual" conditional and filter expression.
        PageIterable<MovieActor> pagedResults = movieActorTable.query(tableQuery);
        logger.info("page count: {}", pagedResults.stream().count()); // Log  number of pages.

        pagedResults.items().stream()
                .sorted()
                .forEach(
                        item -> logger.info(item.toString()) // Log the sorted list of items.
                );
```

**Example – 使用 `keyEqual` 查询条件的输出**  
下面是运行该方法的输出。该输出显示 `movieName` 值为 **movie01** 的项目，不显示 `actingSchoolName` 等于 **`null`** 的项目。  

```
2023-03-05 13:11:05 [main] INFO  org.example.tests.QueryDemo:46 - page count: 1
2023-03-05 13:11:05 [main] INFO  org.example.tests.QueryDemo:51 - MovieActor{movieName='movie01', actorName='actor1', actingAward='actingaward1', actingYear=2001, actingSchoolName='actingschool1'}
2023-03-05 13:11:05 [main] INFO  org.example.tests.QueryDemo:51 - MovieActor{movieName='movie01', actorName='actor2', actingAward='actingaward2', actingYear=2001, actingSchoolName='actingschool2'}
2023-03-05 13:11:05 [main] INFO  org.example.tests.QueryDemo:51 - MovieActor{movieName='movie01', actorName='actor4', actingAward='actingaward4', actingYear=2001, actingSchoolName='actingschool4'}
```

#### 按分区键和排序键查询项目
<a name="sort-type-query-conditional-example"></a>

`sortGreaterThanOrEqualTo` `QueryConditional` 通过为大于或等于 **actor2** 的值添加排序键条件来细化分区键匹配（**movie01**）。

以 `sort` 开头的 [`QueryConditional` 方法](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/QueryConditional.html)需要有分区键值才能匹配，并通过基于排序键值的比较来进一步细化查询。方法名称中的 `Sort` 并不意味着结果已排序，而是表示将使用排序键值进行比较。

在以下代码段中，我们更改了之前在注释行 3 之后显示的查询请求。此片段将 “keyEqual” 条件查询替换为注释行 1a 之后定义的 sortGreaterThan OrEqualTo “” 查询条件。以下代码还删除了筛选表达式。

```
        QueryEnhancedRequest tableQuery = QueryEnhancedRequest.builder()
                .queryConditional(sortGreaterThanOrEqualTo).build();
```

**Example – 使用 `sortGreaterThanOrEqualTo` 查询条件的输出**  
以下输出显示了查询的结果。该查询仅返回 `movieName` 值等于 **movie01** 且 `actorName` 值大于或等于 **actor2** 的项目。因为我们移除了筛选条件，因此查询会返回没有 `actingSchoolName` 属性值的项目。  

```
2023-03-05 13:15:00 [main] INFO  org.example.tests.QueryDemo:46 - page count: 1
2023-03-05 13:15:00 [main] INFO  org.example.tests.QueryDemo:51 - MovieActor{movieName='movie01', actorName='actor2', actingAward='actingaward2', actingYear=2001, actingSchoolName='actingschool2'}
2023-03-05 13:15:00 [main] INFO  org.example.tests.QueryDemo:51 - MovieActor{movieName='movie01', actorName='actor3', actingAward='actingaward3', actingYear=2001, actingSchoolName='null'}
2023-03-05 13:15:00 [main] INFO  org.example.tests.QueryDemo:51 - MovieActor{movieName='movie01', actorName='actor4', actingAward='actingaward4', actingYear=2001, actingSchoolName='actingschool4'}
```

# 执行批量操作
<a name="ddb-en-client-use-multiop-batch"></a>

DynamoDB 增强型客户端 API 提供两种批处理方法：[`batchGetItem`()](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedClient.html#batchGetItem(java.util.function.Consumer)) 和 [`batchWriteItem`()](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedClient.html#batchWriteItem(java.util.function.Consumer))。

## `batchGetItem()` 示例
<a name="ddb-en-client-use-multiop-batch-get"></a>

使用 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedClient.html#batchGetItem(java.util.function.Consumer)](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedClient.html#batchGetItem(java.util.function.Consumer)) 方法，您可以在一个总请求中检索多个表中最多 100 个单独的项目。以下示例使用前面显示的 [`Customer`](ddb-en-client-gs-tableschema.md#ddb-en-client-gs-tableschema-anno-bean-cust) 和 [`MovieActor`](ddb-en-client-use-multirecord.md#ddb-en-client-use-movieactor-class) 数据类。

在示例的注释行 1 和 2 之后，您将构建 `[ReadBatch](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/ReadBatch.html)` 对象，然后在注释行 3 之后将其作为参数添加到 `batchGetItem()` 方法中。

注释行 1 之后的代码生成要从 `Customer` 表中读取的批次。注释行 1a 之后的代码显示了如何使用 `[GetItemEnhancedRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/GetItemEnhancedRequest.Builder.html)` 生成器，该生成器采用主键值和排序键值来指定要读取的项目。如果数据类有复合键，则必须同时提供分区键值和排序键值。

与指定键值来请求项目不同，您可以使用数据类来请求项目，如注释行 1b 后所示。提交请求之前，SDK 会在后台提取键值。

如 2a 之后的两个语句所示，当您使用基于键的方法指定项目时，您还可以指定 DynamoDB 应执行[强一致性读取](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.ReadConsistency.html)。使用 `consistentRead()` 方法时，必须对同一个表的所有请求项目使用该方法。

要检索 DynamoDB 找到的项目，请使用注释行 4 之后显示的 `[resultsForTable() ](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/BatchGetResultPage.html#resultsForTable(software.amazon.awssdk.enhanced.dynamodb.MappedTableResource))` 方法。为请求中读取的每个表调用该方法。`resultsForTable()` 返回可使用任何 `java.util.List` 方法处理的已找到项目的列表。此示例记录每个项目。

要发现 DynamoDB 未处理的项目，请使用注释行 5 之后的方法。`BatchGetResultPage` 类具有允许您访问每个未处理键的 `[unprocessedKeysForTable()](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/BatchGetResultPage.html#unprocessedKeysForTable(software.amazon.awssdk.enhanced.dynamodb.MappedTableResource))` 方法。[BatchGetItem API 参考](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_BatchGetItem.html)提供了有关导致项目未处理的情况的更多信息。

```
    public static void batchGetItemExample(DynamoDbEnhancedClient enhancedClient,
                                           DynamoDbTable<Customer> customerTable,
                                           DynamoDbTable<MovieActor> movieActorTable) {

        Customer customer2 = new Customer();
        customer2.setId("2");
        customer2.setEmail("cust2@example.org");

        // 1. Build a batch to read from the Customer table.
        ReadBatch customerBatch = ReadBatch.builder(Customer.class)
                .mappedTableResource(customerTable)
                // 1a. Specify the primary key value and sort key value for the item.
                .addGetItem(b -> b.key(k -> k.partitionValue("1").sortValue("cust1@orgname.org")))
                // 1b. Alternatively, supply a data class instances to provide the primary key values.
                .addGetItem(customer2)
                .build();

        // 2. Build a batch to read from the MovieActor table.
        ReadBatch moveActorBatch = ReadBatch.builder(MovieActor.class)
                .mappedTableResource(movieActorTable)
                // 2a. Call consistentRead(Boolean.TRUE) for each item for the same table.
                .addGetItem(b -> b.key(k -> k.partitionValue("movie01").sortValue("actor1")).consistentRead(Boolean.TRUE))
                .addGetItem(b -> b.key(k -> k.partitionValue("movie01").sortValue("actor4")).consistentRead(Boolean.TRUE))
                .build();

        // 3. Add ReadBatch objects to the request.
        BatchGetResultPageIterable resultPages = enhancedClient.batchGetItem(b -> b.readBatches(customerBatch, moveActorBatch));

        // 4. Retrieve the successfully requested items from each table.
        resultPages.resultsForTable(customerTable).forEach(item -> logger.info(item.toString()));
        resultPages.resultsForTable(movieActorTable).forEach(item -> logger.info(item.toString()));

        // 5. Retrieve the keys of the items requested but not processed by the service.
        resultPages.forEach((BatchGetResultPage pageResult) -> {
            pageResult.unprocessedKeysForTable(customerTable).forEach(key -> logger.info("Unprocessed item key: " + key.toString()));
            pageResult.unprocessedKeysForTable(movieActorTable).forEach(key -> logger.info("Unprocessed item key: " + key.toString()));
        });
    }
```

在运行示例代码之前，假设两个表中包含以下项目。

### 表格中的项目
<a name="ddb-en-client-use-multiop-batch-get-tableitems"></a>

```
Customer [id=1, name=CustName1, email=cust1@example.org, regDate=2023-03-31T15:46:27.688Z]
Customer [id=2, name=CustName2, email=cust2@example.org, regDate=2023-03-31T15:46:28.688Z]
Customer [id=3, name=CustName3, email=cust3@example.org, regDate=2023-03-31T15:46:29.688Z]
Customer [id=4, name=CustName4, email=cust4@example.org, regDate=2023-03-31T15:46:30.688Z]
Customer [id=5, name=CustName5, email=cust5@example.org, regDate=2023-03-31T15:46:31.689Z]
MovieActor{movieName='movie01', actorName='actor0', actingAward='actingaward0', actingYear=2001, actingSchoolName='null'}
MovieActor{movieName='movie01', actorName='actor1', actingAward='actingaward1', actingYear=2001, actingSchoolName='actingschool1'}
MovieActor{movieName='movie01', actorName='actor2', actingAward='actingaward2', actingYear=2001, actingSchoolName='actingschool2'}
MovieActor{movieName='movie01', actorName='actor3', actingAward='actingaward3', actingYear=2001, actingSchoolName='null'}
MovieActor{movieName='movie01', actorName='actor4', actingAward='actingaward4', actingYear=2001, actingSchoolName='actingschool4'}
```

以下输出显示了在注释行 4 之后返回和记录的项目。

```
Customer [id=1, name=CustName1, email=cust1@example.org, regDate=2023-03-31T15:46:27.688Z]
Customer [id=2, name=CustName2, email=cust2@example.org, regDate=2023-03-31T15:46:28.688Z]
MovieActor{movieName='movie01', actorName='actor4', actingAward='actingaward4', actingYear=2001, actingSchoolName='actingschool4'}
MovieActor{movieName='movie01', actorName='actor1', actingAward='actingaward1', actingYear=2001, actingSchoolName='actingschool1'}
```

## `batchWriteItem()` 示例
<a name="ddb-en-client-use-multiop-batch-write"></a>

`batchWriteItem()` 方法在一个或多个表中放置或删除多个项目。您最多可以在请求中指定 25 个单独的放置或删除操作。以下示例使用前面显示的 [`ProductCatalog`](ddb-en-client-use.md#ddb-en-client-use-compare-cs3) 和 [`MovieActor`](ddb-en-client-use-multirecord.md#ddb-en-client-use-movieactor-class) 模型类。

`WriteBatch` 对象是在注释行 1 和 2 之后生成的。对于 `ProductCatalog` 表，代码放置一个项目并删除一个项目。对于注释行 2 之后的 `MovieActor` 表，代码放置了两个项目并删除了一个项目。

在注释行 3 之后调用 `batchWriteItem` 方法。`[builder](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/BatchWriteItemEnhancedRequest.Builder.html)` 参数提供每个表的批处理请求。

返回的 `[BatchWriteResult](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/BatchWriteResult.html)` 对象为每个操作提供了不同的方法来查看未处理的请求。注释行 4a 之后的代码为未处理的删除请求提供了键，注释行 4b 之后的代码提供了未处理的放置项目。

```
    public static void batchWriteItemExample(DynamoDbEnhancedClient enhancedClient,
                                             DynamoDbTable<ProductCatalog> catalogTable,
                                             DynamoDbTable<MovieActor> movieActorTable) {

        // 1. Build a batch to write to the ProductCatalog table.
        WriteBatch products = WriteBatch.builder(ProductCatalog.class)
                .mappedTableResource(catalogTable)
                .addPutItem(b -> b.item(getProductCatItem1()))
                .addDeleteItem(b -> b.key(k -> k
                        .partitionValue(getProductCatItem2().id())
                        .sortValue(getProductCatItem2().title())))
                .build();

        // 2. Build a batch to write to the MovieActor table.
        WriteBatch movies = WriteBatch.builder(MovieActor.class)
                .mappedTableResource(movieActorTable)
                .addPutItem(getMovieActorYeoh())
                .addPutItem(getMovieActorBlanchettPartial())
                .addDeleteItem(b -> b.key(k -> k
                        .partitionValue(getMovieActorStreep().getMovieName())
                        .sortValue(getMovieActorStreep().getActorName())))
                .build();

        // 3. Add WriteBatch objects to the request.
        BatchWriteResult batchWriteResult = enhancedClient.batchWriteItem(b -> b.writeBatches(products, movies));
        // 4. Retrieve keys for items the service did not process.
        // 4a. 'unprocessedDeleteItemsForTable()' returns keys for delete requests that did not process.
        if (batchWriteResult.unprocessedDeleteItemsForTable(movieActorTable).size() > 0) {
            batchWriteResult.unprocessedDeleteItemsForTable(movieActorTable).forEach(key ->
                    logger.info(key.toString()));
        }
        // 4b. 'unprocessedPutItemsForTable()' returns keys for put requests that did not process.
        if (batchWriteResult.unprocessedPutItemsForTable(catalogTable).size() > 0) {
            batchWriteResult.unprocessedPutItemsForTable(catalogTable).forEach(key ->
                    logger.info(key.toString()));
        }
    }
```

以下帮助程序方法为放置和删除操作提供了模型对象。

### 帮助程序方法
<a name="ddb-en-client-use-multiop-batch-write-helpers"></a>

```
 1.     public static ProductCatalog getProductCatItem1() {
 2.         return ProductCatalog.builder()
 3.                 .id(2)
 4.                 .isbn("1-565-85698")
 5.                 .authors(new HashSet<>(Arrays.asList("a", "b")))
 6.                 .price(BigDecimal.valueOf(30.22))
 7.                 .title("Title 55")
 8.                 .build();
 9.     }
10. 
11.     public static ProductCatalog getProductCatItem2() {
12.         return ProductCatalog.builder()
13.                 .id(4)
14.                 .price(BigDecimal.valueOf(40.00))
15.                 .title("Title 1")
16.                 .build();
17.     }  
18. 
19.     public static MovieActor getMovieActorBlanchettPartial() {
20.         MovieActor movieActor = new MovieActor();
21.         movieActor.setActorName("Cate Blanchett");
22.         movieActor.setMovieName("Blue Jasmine");
23.         movieActor.setActingYear(2023);
24.         movieActor.setActingAward("Best Actress");
25.         return movieActor;
26.     }
27. 
28.     public static MovieActor getMovieActorStreep() {
29.         MovieActor movieActor = new MovieActor();
30.         movieActor.setActorName("Meryl Streep");
31.         movieActor.setMovieName("Sophie's Choice");
32.         movieActor.setActingYear(1982);
33.         movieActor.setActingAward("Best Actress");
34.         movieActor.setActingSchoolName("Yale School of Drama");
35.         return movieActor;
36.     }
37. 
38.     public static MovieActor getMovieActorYeoh(){
39.         MovieActor movieActor = new MovieActor();
40.         movieActor.setActorName("Michelle Yeoh");
41.         movieActor.setMovieName("Everything Everywhere All at Once");
42.         movieActor.setActingYear(2023);
43.         movieActor.setActingAward("Best Actress");
44.         movieActor.setActingSchoolName("Royal Academy of Dance");
45.         return movieActor;
46.     }
```

在运行示例代码之前，假设这些表包含以下项目。

```
MovieActor{movieName='Blue Jasmine', actorName='Cate Blanchett', actingAward='Best Actress', actingYear=2013, actingSchoolName='National Institute of Dramatic Art'}
MovieActor{movieName='Sophie's Choice', actorName='Meryl Streep', actingAward='Best Actress', actingYear=1982, actingSchoolName='Yale School of Drama'}
ProductCatalog{id=4, title='Title 1', isbn='orig_isbn', authors=[b, g], price=10}
```

示例代码完成后，表中将包含以下项目。

```
MovieActor{movieName='Blue Jasmine', actorName='Cate Blanchett', actingAward='Best Actress', actingYear=2013, actingSchoolName='null'}
MovieActor{movieName='Everything Everywhere All at Once', actorName='Michelle Yeoh', actingAward='Best Actress', actingYear=2023, actingSchoolName='Royal Academy of Dance'}
ProductCatalog{id=2, title='Title 55', isbn='1-565-85698', authors=[a, b], price=30.22}
```

请注意，在 `MovieActor` 表中，`Blue Jasmine` 电影项目已被替换为通过 `getMovieActorBlanchettPartial()` 帮助程序方法获取的放置请求中使用的项目。如果未提供数据 Bean 属性值，则数据库中的值将被删除。这就是为什么 `Blue Jasmine` 影片项目的结果 `actingSchoolName` 为空的原因。

**注意**  
尽管 API 文档表示可以使用条件表达式，并且可以在单独的[放置](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/PutItemEnhancedRequest.html)和[删除](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/DeleteItemEnhancedRequest.html)请求中返回已消耗的容量和集合指标，但在批量写入场景中，情况并非如此。为了提高批处理操作的性能，将忽略这些单独的选项。

# 执行事务操作
<a name="ddb-en-client-use-multiop-trans"></a>

DynamoDB 增强型客户端 API 提供了 `transactGetItems()` 和 `transactWriteItems()` 方法。适用于 Java 的 SDK 的事务方法在 DynamoDB 表中提供原子性、一致性、隔离性和持久性 (ACID)，帮助您维护应用程序中的数据正确性。

## `transactGetItems()` 示例
<a name="ddb-en-client-use-multiop-trans-getitems"></a>

`[transactGetItems()](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedClient.html#transactGetItems(java.util.function.Consumer))` 方法最多可接受 100 个单独的项目请求。所有项目都在单个原子事务中读取。《Amazon DynamoDB 开发人员指南》**包含有关[导致 `transactGetItems()` 方法失败的条件](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/transaction-apis.html#transaction-apis-txgetitems)以及您调用 `[transactGetItem()](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/transaction-apis.html#transaction-isolation)` 时使用的隔离级别的信息。

在以下示例的注释行 1 之后，代码使用 `[builder](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/TransactGetItemsEnhancedRequest.Builder.html)` 参数调用 `transactGetItems()` 方法。使用一个数据对象调用生成器的 `[addGetItem()](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/TransactGetItemsEnhancedRequest.Builder.html#addGetItem(software.amazon.awssdk.enhanced.dynamodb.MappedTableResource,T))` 三次，该数据对象包含 SDK 将用于生成最终请求的键值。

该请求在注释行 2 之后返回 `[Document](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/Document.html)` 对象列表。返回的文档列表包含项目数据的非空 [Document](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/Document.html) 实例，其顺序与请求的顺序相同。如果返回了项目数据，则 `[Document.getItem(MappedTableResource<T> mappedTableResource)](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/Document.html#getItem(software.amazon.awssdk.enhanced.dynamodb.MappedTableResource))` 方法会将非类型化 `Document` 对象转换为类型化的 Java 对象，否则该方法返回 null。

```
    public static void transactGetItemsExample(DynamoDbEnhancedClient enhancedClient,
                                               DynamoDbTable<ProductCatalog> catalogTable,
                                               DynamoDbTable<MovieActor> movieActorTable) {

        // 1. Request three items from two tables using a builder.
        final List<Document> documents = enhancedClient.transactGetItems(b -> b
                .addGetItem(catalogTable, Key.builder().partitionValue(2).sortValue("Title 55").build())
                .addGetItem(movieActorTable, Key.builder().partitionValue("Sophie's Choice").sortValue("Meryl Streep").build())
                .addGetItem(movieActorTable, Key.builder().partitionValue("Blue Jasmine").sortValue("Cate Blanchett").build())
                .build());

        // 2. A list of Document objects is returned in the same order as requested.
        ProductCatalog title55 = documents.get(0).getItem(catalogTable);
        if (title55 != null) {
            logger.info(title55.toString());
        }

        MovieActor sophiesChoice = documents.get(1).getItem(movieActorTable);
        if (sophiesChoice != null) {
            logger.info(sophiesChoice.toString());
        }

        // 3. The getItem() method returns null if the Document object contains no item from DynamoDB.
        MovieActor blueJasmine = documents.get(2).getItem(movieActorTable);
        if (blueJasmine != null) {
            logger.info(blueJasmine.toString());
        }
    }
```

在代码示例运行之前，DynamoDB 表包含以下项目。

```
ProductCatalog{id=2, title='Title 55', isbn='orig_isbn', authors=[b, g], price=10}
MovieActor{movieName='Sophie's Choice', actorName='Meryl Streep', actingAward='Best Actress', actingYear=1982, actingSchoolName='Yale School of Drama'}
```

记录了以下输出。如果请求了某个项目但未找到，则该项目不会返回（像请求名为 `Blue Jasmine` 的电影那样）。

```
ProductCatalog{id=2, title='Title 55', isbn='orig_isbn', authors=[b, g], price=10}
MovieActor{movieName='Sophie's Choice', actorName='Meryl Streep', actingAward='Best Actress', actingYear=1982, actingSchoolName='Yale School of Drama'}
```

## `transactWriteItems()` 示例
<a name="ddb-en-client-use-multiop-trans-writeitems"></a>

`[transactWriteItems()](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedClient.html#transactWriteItems(java.util.function.Consumer))` 在跨多个表的单个原子事务中最多接受 100 个放置、更新或删除操作。《Amazon DynamoDB 开发人员指南》**包含有关[底层 DynamoDB 服务操作](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/transaction-apis.html#transaction-apis-txwriteitems)的限制和失败条件的详细信息。

### 基本示例
<a name="ddb-en-client-use-multiop-trans-writeitems-basic"></a>

在以下示例中，请求对两个表执行四个操作。之前已显示了相应的模型类 [`ProductCatalog`](ddb-en-client-use.md#ddb-en-client-use-compare-cs3) 和 [`MovieActor`](ddb-en-client-use-multirecord.md#ddb-en-client-use-movieactor-class)。

三种可能的操作（放置、更新和删除）均使用专用的请求参数来指定详细信息。

注释行 1 之后的代码显示了 `addPutItem()` 方法的简单变体。该方法接受要放置的 `[MappedTableResource](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/MappedTableResource.html)` 对象和数据对象实例。注释行 2 之后的语句显示了接受 `[TransactPutItemEnhancedRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/TransactPutItemEnhancedRequest.html)` 实例的变体。此变体允许您在请求中添加更多选项，例如条件表达式。随后的[示例](#ddb-en-client-use-multiop-trans-writeitems-opcondition)显示了单个操作的条件表达式。

在注释行 3 之后请求更新操作。`[TransactUpdateItemEnhancedRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/TransactUpdateItemEnhancedRequest.Builder.html)` 有一种 `ignoreNulls()` 方法可以让您配置 SDK 如何处理模型对象上的 `null` 值。如果 `ignoreNulls()` 方法返回 true，则对于数据对象属性为 `null` 的情况，SDK 不会移除表的属性值。如果 `ignoreNulls()` 方法返回 false，则 SDK 会请求 DynamoDB 服务从表中的项目中移除这些属性。`ignoreNulls` 的默认值为 false。

注释行 4 之后的语句显示了接受数据对象的删除请求的变体。增强型客户端在分派最终请求之前提取键值。

```
    public static void transactWriteItems(DynamoDbEnhancedClient enhancedClient,
                                          DynamoDbTable<ProductCatalog> catalogTable,
                                          DynamoDbTable<MovieActor> movieActorTable) {

        enhancedClient.transactWriteItems(b -> b
                // 1. Simplest variation of put item request.
                .addPutItem(catalogTable, getProductCatId2())
                // 2. Put item request variation that accommodates condition expressions.
                .addPutItem(movieActorTable, TransactPutItemEnhancedRequest.builder(MovieActor.class)
                        .item(getMovieActorStreep())
                        .conditionExpression(Expression.builder().expression("attribute_not_exists (movie)").build())
                        .build())
                // 3. Update request that does not remove attribute values on the table if the data object's value is null.
                .addUpdateItem(catalogTable, TransactUpdateItemEnhancedRequest.builder(ProductCatalog.class)
                        .item(getProductCatId4ForUpdate())
                        .ignoreNulls(Boolean.TRUE)
                        .build())
                // 4. Variation of delete request that accepts a data object. The key values are extracted for the request.
                .addDeleteItem(movieActorTable, getMovieActorBlanchett())
        );
    }
```

以下帮助程序方法为 `add*Item` 参数提供了数据对象。

#### 帮助程序方法
<a name="ddb-en-client-use-multiop-trans-writeitems-basic-helpers"></a>

```
    public static ProductCatalog getProductCatId2() {
        return ProductCatalog.builder()
                .id(2)
                .isbn("1-565-85698")
                .authors(new HashSet<>(Arrays.asList("a", "b")))
                .price(BigDecimal.valueOf(30.22))
                .title("Title 55")
                .build();
    }

    public static ProductCatalog getProductCatId4ForUpdate() {
        return ProductCatalog.builder()
                .id(4)
                .price(BigDecimal.valueOf(40.00))
                .title("Title 1")
                .build();
    }

    public static MovieActor getMovieActorBlanchett() {
        MovieActor movieActor = new MovieActor();
        movieActor.setActorName("Cate Blanchett");
        movieActor.setMovieName("Tar");
        movieActor.setActingYear(2022);
        movieActor.setActingAward("Best Actress");
        movieActor.setActingSchoolName("National Institute of Dramatic Art");
        return movieActor;
    }

    public static MovieActor getMovieActorStreep() {
        MovieActor movieActor = new MovieActor();
        movieActor.setActorName("Meryl Streep");
        movieActor.setMovieName("Sophie's Choice");
        movieActor.setActingYear(1982);
        movieActor.setActingAward("Best Actress");
        movieActor.setActingSchoolName("Yale School of Drama");
        return movieActor;
    }
```

在代码示例运行之前，DynamoDB 表包含以下项目。

```
1 | ProductCatalog{id=4, title='Title 1', isbn='orig_isbn', authors=[b, g], price=10}
2 | MovieActor{movieName='Tar', actorName='Cate Blanchett', actingAward='Best Actress', actingYear=2022, actingSchoolName='National Institute of Dramatic Art'}
```

代码运行完毕后，表中将显示以下项目。

```
3 | ProductCatalog{id=2, title='Title 55', isbn='1-565-85698', authors=[a, b], price=30.22}
4 | ProductCatalog{id=4, title='Title 1', isbn='orig_isbn', authors=[b, g], price=40.0}
5 | MovieActor{movieName='Sophie's Choice', actorName='Meryl Streep', actingAward='Best Actress', actingYear=1982, actingSchoolName='Yale School of Drama'}
```

第 2 行的项目已被删除，第 3 行和第 5 行显示了已放置的项目。第 4 行显示第 1 行的更新。`price` 值是该项目上唯一更改的值。如果 `ignoreNulls()` 返回 false，第 4 行将类似于以下行。

```
ProductCatalog{id=4, title='Title 1', isbn='null', authors=null, price=40.0}
```

### 条件检查示例
<a name="ddb-en-client-use-multiop-trans-writeitems-checkcond"></a>

以下示例演示条件检查的使用。条件检查用于检查项目是否存在，或者检查数据库中项目特定属性的条件。条件检查中检查的项目不能用于事务中的其他操作。

**注意**  
您不能将同一个事务中的多个操作指向同一个项目。例如，您不能在同一个事务中对相同项目既执行条件检查又执行更新操作。

该示例演示事务写入项目请求中的每种操作类型。在注释行 2 之后，如果 `conditionExpression` 参数的计算结果为 `false`，则 `addConditionCheck()` 方法会提供事务失败的条件。从帮助程序方法块中显示的方法返回的条件表达式会检查电影 `Sophie's Choice` 的获奖年份是否不等于 `1982`。如果是，则表达式的计算结果为 `false`，事务将失败。

本指南的另一个主题深入讨论了[表达式](ddb-en-client-expressions.md)。

```
    public static void conditionCheckFailExample(DynamoDbEnhancedClient enhancedClient,
                                                 DynamoDbTable<ProductCatalog> catalogTable,
                                                 DynamoDbTable<MovieActor> movieActorTable) {

        try {
            enhancedClient.transactWriteItems(b -> b
                    // 1. Perform one of each type of operation with the next three methods.
                    .addPutItem(catalogTable, TransactPutItemEnhancedRequest.builder(ProductCatalog.class)
                            .item(getProductCatId2()).build())
                    .addUpdateItem(catalogTable, TransactUpdateItemEnhancedRequest.builder(ProductCatalog.class)
                            .item(getProductCatId4ForUpdate())
                            .ignoreNulls(Boolean.TRUE).build())
                    .addDeleteItem(movieActorTable, TransactDeleteItemEnhancedRequest.builder()
                            .key(b1 -> b1
                                    .partitionValue(getMovieActorBlanchett().getMovieName())
                                    .sortValue(getMovieActorBlanchett().getActorName())).build())
                    // 2. Add a condition check on a table item that is not involved in another operation in this request.
                    .addConditionCheck(movieActorTable, ConditionCheck.builder()
                            .conditionExpression(buildConditionCheckExpression())
                            .key(k -> k
                                    .partitionValue("Sophie's Choice")
                                    .sortValue("Meryl Streep"))
                            // 3. Specify the request to return existing values from the item if the condition evaluates to true.
                            .returnValuesOnConditionCheckFailure(ReturnValuesOnConditionCheckFailure.ALL_OLD)
                            .build())
                    .build());
        // 4. Catch the exception if the transaction fails and log the information.
        } catch (TransactionCanceledException ex) {
            ex.cancellationReasons().stream().forEach(cancellationReason -> {
                logger.info(cancellationReason.toString());
            });
        }
    }
```

前面的代码示例中使用了以下帮助程序方法。

#### 帮助程序方法
<a name="ddb-en-client-use-multiop-trans-writeitems-checkcond-helpers"></a>

```
    private static Expression buildConditionCheckExpression() {
        Map<String, AttributeValue> expressionValue = Map.of(
                ":year", numberValue(1982));

        return Expression.builder()
                .expression("actingyear <> :year")
                .expressionValues(expressionValue)
                .build();
    }

    public static ProductCatalog getProductCatId2() {
        return ProductCatalog.builder()
                .id(2)
                .isbn("1-565-85698")
                .authors(new HashSet<>(Arrays.asList("a", "b")))
                .price(BigDecimal.valueOf(30.22))
                .title("Title 55")
                .build();
    }

    public static ProductCatalog getProductCatId4ForUpdate() {
        return ProductCatalog.builder()
                .id(4)
                .price(BigDecimal.valueOf(40.00))
                .title("Title 1")
                .build();
    }

    public static MovieActor getMovieActorBlanchett() {
        MovieActor movieActor = new MovieActor();
        movieActor.setActorName("Cate Blanchett");
        movieActor.setMovieName("Blue Jasmine");
        movieActor.setActingYear(2013);
        movieActor.setActingAward("Best Actress");
        movieActor.setActingSchoolName("National Institute of Dramatic Art");
        return movieActor;
    }
```

在代码示例运行之前，DynamoDB 表包含以下项目。

```
1 | ProductCatalog{id=4, title='Title 1', isbn='orig_isbn', authors=[b, g], price=10}
2 | MovieActor{movieName='Sophie's Choice', actorName='Meryl Streep', actingAward='Best Actress', actingYear=1982, actingSchoolName='Yale School of Drama'}
3 | MovieActor{movieName='Tar', actorName='Cate Blanchett', actingAward='Best Actress', actingYear=2022, actingSchoolName='National Institute of Dramatic Art'}
```

代码运行完毕后，表中将显示以下项目。

```
ProductCatalog{id=4, title='Title 1', isbn='orig_isbn', authors=[b, g], price=10}
MovieActor{movieName='Sophie's Choice', actorName='Meryl Streep', actingAward='Best Actress', actingYear=1982, actingSchoolName='Yale School of Drama'}
MovieActor{movieName='Tar', actorName='Cate Blanchett', actingAward='Best Actress', actingYear=2022, actingSchoolName='National Institute of Dramatic Art'}
```

由于事务失败，表中的项目保持不变。影片 `Sophie's Choice` 的 `actingYear` 值为 `1982`，如调用 `transactWriteItem()` 方法之前表中项目的第 2 行所示。

要捕获事务的取消信息，请将 `transactWriteItems()` 方法调用封装在一个 `try` 块中，然后 `catch` [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/TransactionCanceledException.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/TransactionCanceledException.html)。在示例的注释行 4 之后，代码会记录每个 `[CancellationReason](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/CancellationReason.html)` 对象。由于示例注释行 3 之后的代码指定应返回导致事务失败的项目的值，因此日志会显示电影项目 `Sophie's Choice` 的原始数据库值。

```
CancellationReason(Code=None)
CancellationReason(Code=None)
CancellationReason(Code=None)
CancellationReason(Item={actor=AttributeValue(S=Meryl Streep), movie=AttributeValue(S=Sophie's Choice), actingaward=AttributeValue(S=Best Actress), actingyear=AttributeValue(N=1982), actingschoolname=AttributeValue(S=Yale School of Drama)}, ¬
    Code=ConditionalCheckFailed, Message=The conditional request failed.)
```

### 单一操作条件示例
<a name="ddb-en-client-use-multiop-trans-writeitems-opcondition"></a>

以下示例演示在事务请求中对单个操作使用条件的情况。注释行 1 之后的删除操作包含一个条件，该条件用于根据数据库检查操作的目标项目的值。在此示例中，在注释行 2 之后使用帮助程序方法创建的条件表达式指定，如果电影的上映年份不等于 2013 年，则应从数据库中删除该项目。

本指南稍后将讨论[表达式](ddb-en-client-expressions.md)。

```
    public static void singleOperationConditionFailExample(DynamoDbEnhancedClient enhancedClient,
                                                           DynamoDbTable<ProductCatalog> catalogTable,
                                                           DynamoDbTable<MovieActor> movieActorTable) {
        try {
            enhancedClient.transactWriteItems(b -> b
                    .addPutItem(catalogTable, TransactPutItemEnhancedRequest.builder(ProductCatalog.class)
                            .item(getProductCatId2())
                            .build())
                    .addUpdateItem(catalogTable, TransactUpdateItemEnhancedRequest.builder(ProductCatalog.class)
                            .item(getProductCatId4ForUpdate())
                            .ignoreNulls(Boolean.TRUE).build())
                    // 1. Delete operation that contains a condition expression
                    .addDeleteItem(movieActorTable, TransactDeleteItemEnhancedRequest.builder()
                            .key((Key.Builder k) -> {
                                MovieActor blanchett = getMovieActorBlanchett();
                                k.partitionValue(blanchett.getMovieName())
                                        .sortValue(blanchett.getActorName());
                            })
                            .conditionExpression(buildDeleteItemExpression())
                            .returnValuesOnConditionCheckFailure(ReturnValuesOnConditionCheckFailure.ALL_OLD)
                            .build())
                    .build());
        } catch (TransactionCanceledException ex) {
            ex.cancellationReasons().forEach(cancellationReason -> logger.info(cancellationReason.toString()));
        }
    }

    // 2. Provide condition expression to check if 'actingyear' is not equal to 2013.
    private static Expression buildDeleteItemExpression() {
        Map<String, AttributeValue> expressionValue = Map.of(
                ":year", numberValue(2013));

        return Expression.builder()
                .expression("actingyear <> :year")
                .expressionValues(expressionValue)
                .build();
    }
```

前面的代码示例中使用了以下帮助程序方法。

#### 帮助程序方法
<a name="ddb-en-client-use-multiop-trans-writeitems-opcondition-helpers"></a>

```
    public static ProductCatalog getProductCatId2() {
        return ProductCatalog.builder()
                .id(2)
                .isbn("1-565-85698")
                .authors(new HashSet<>(Arrays.asList("a", "b")))
                .price(BigDecimal.valueOf(30.22))
                .title("Title 55")
                .build();
    }

    public static ProductCatalog getProductCatId4ForUpdate() {
        return ProductCatalog.builder()
                .id(4)
                .price(BigDecimal.valueOf(40.00))
                .title("Title 1")
                .build();
    }
    public static MovieActor getMovieActorBlanchett() {
        MovieActor movieActor = new MovieActor();
        movieActor.setActorName("Cate Blanchett");
        movieActor.setMovieName("Blue Jasmine");
        movieActor.setActingYear(2013);
        movieActor.setActingAward("Best Actress");
        movieActor.setActingSchoolName("National Institute of Dramatic Art");
        return movieActor;
    }
```

在代码示例运行之前，DynamoDB 表包含以下项目。

```
1 | ProductCatalog{id=4, title='Title 1', isbn='orig_isbn', authors=[b, g], price=10}
2 | MovieActor{movieName='Blue Jasmine', actorName='Cate Blanchett', actingAward='Best Actress', actingYear=2013, actingSchoolName='National Institute of Dramatic Art'}
```

代码运行完毕后，表中将显示以下项目。

```
ProductCatalog{id=4, title='Title 1', isbn='orig_isbn', authors=[b, g], price=10}
2023-03-15 11:29:07 [main] INFO  org.example.tests.TransactDemoTest:168 - MovieActor{movieName='Blue Jasmine', actorName='Cate Blanchett', actingAward='Best Actress', actingYear=2013, actingSchoolName='National Institute of Dramatic Art'}
```

由于事务失败，表中的项目保持不变。在代码示例运行之前，影片 `Blue Jasmine` 的 `actingYear` 值为 `2013`，如项目列表第 2 行所示。

以下行记录到控制台。

```
CancellationReason(Code=None)
CancellationReason(Code=None)
CancellationReason(Item={actor=AttributeValue(S=Cate Blanchett), movie=AttributeValue(S=Blue Jasmine), actingaward=AttributeValue(S=Best Actress), actingyear=AttributeValue(N=2013), actingschoolname=AttributeValue(S=National Institute of Dramatic Art)}, 
    Code=ConditionalCheckFailed, Message=The conditional request failed)
```

# 使用二级索引
<a name="ddb-en-client-use-secindex"></a>

二级索引通过定义在查询和扫描操作中使用的备用键来改善数据访问。全局二级索引 (GSI) 的分区键和排序键可与基表不同。相比之下，本地二级索引 (LSI) 使用主索引的分区键。

## 使用二级索引注释对数据类进行注释
<a name="ddb-en-client-use-secindex-annomodel"></a>

参与二级索引的属性需要使用 `@DynamoDbSecondaryPartitionKey` 或 `@DynamoDbSecondarySortKey` 注释。

以下类显示了两个索引的注释。命名的 GSI *SubjectLastPostedDateIndex*使用该`Subject`属性作为分区键，使用属性`LastPostedDateTime`作为排序键。命名的 LSI *ForumLastPostedDateIndex*使用`ForumName`作为其分区键和`LastPostedDateTime`排序键。

请注意，`Subject` 属性起着双重作用。它是主键的排序键，也是命名*SubjectLastPostedDateIndex*的 GSI 的分区键。

### `MessageThread` 类
<a name="ddb-en-client-use-secindex-class"></a>

`MessageThread` 类适合用作《*Amazon DynamoDB 开发人员指南*》中[示例 Thread 表](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/AppendixSampleTables.html)的数据类。

#### 导入
<a name="ddb-en-client-use-secindex-classimports"></a>

```
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbBean;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbPartitionKey;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSecondaryPartitionKey;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSecondarySortKey;
import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.DynamoDbSortKey;

import java.util.List;
```

```
@DynamoDbBean
public class MessageThread {
    private String ForumName;
    private String Subject;
    private String Message;
    private String LastPostedBy;
    private String LastPostedDateTime;
    private Integer Views;
    private Integer Replies;
    private Integer Answered;
    private List<String> Tags;

    @DynamoDbPartitionKey
    public String getForumName() {
        return ForumName;
    }

    public void setForumName(String forumName) {
        ForumName = forumName;
    }

    // Sort key for primary index and partition key for GSI "SubjectLastPostedDateIndex".
    @DynamoDbSortKey
    @DynamoDbSecondaryPartitionKey(indexNames = "SubjectLastPostedDateIndex")
    public String getSubject() {
        return Subject;
    }

    public void setSubject(String subject) {
        Subject = subject;
    }

    // Sort key for GSI "SubjectLastPostedDateIndex" and sort key for LSI "ForumLastPostedDateIndex".
    @DynamoDbSecondarySortKey(indexNames = {"SubjectLastPostedDateIndex", "ForumLastPostedDateIndex"})
    public String getLastPostedDateTime() {
        return LastPostedDateTime;
    }

    public void setLastPostedDateTime(String lastPostedDateTime) {
        LastPostedDateTime = lastPostedDateTime;
    }
    public String getMessage() {
        return Message;
    }

    public void setMessage(String message) {
        Message = message;
    }

    public String getLastPostedBy() {
        return LastPostedBy;
    }

    public void setLastPostedBy(String lastPostedBy) {
        LastPostedBy = lastPostedBy;
    }

    public Integer getViews() {
        return Views;
    }

    public void setViews(Integer views) {
        Views = views;
    }

    @DynamoDbSecondaryPartitionKey(indexNames = "ForumRepliesIndex")
    public Integer getReplies() {
        return Replies;
    }

    public void setReplies(Integer replies) {
        Replies = replies;
    }

    public Integer getAnswered() {
        return Answered;
    }

    public void setAnswered(Integer answered) {
        Answered = answered;
    }

    public List<String> getTags() {
        return Tags;
    }

    public void setTags(List<String> tags) {
        Tags = tags;
    }

    public MessageThread() {
        this.Answered = 0;
        this.LastPostedBy = "";
        this.ForumName = "";
        this.Message = "";
        this.LastPostedDateTime = "";
        this.Replies = 0;
        this.Views = 0;
        this.Subject = "";
    }

    @Override
    public String toString() {
        return "MessageThread{" +
                "ForumName='" + ForumName + '\'' +
                ", Subject='" + Subject + '\'' +
                ", Message='" + Message + '\'' +
                ", LastPostedBy='" + LastPostedBy + '\'' +
                ", LastPostedDateTime='" + LastPostedDateTime + '\'' +
                ", Views=" + Views +
                ", Replies=" + Replies +
                ", Answered=" + Answered +
                ", Tags=" + Tags +
                '}';
    }
}
```

## 创建索引
<a name="ddb-en-client-use-secindex-confindex"></a>

从适用于 Java 的 SDK 的 2.20.86 版本开始，`createTable()` 方法会自动根据数据类标注生成二级索引。默认情况下，基表中的所有属性都将复制到索引中，预置吞吐量值为 20 个读取容量单位和 20 个写入容量单位。

但是，如果您使用的是 2.20.86 之前的 SDK 版本，则需要将索引与表一起构建，如以下示例所示。此示例为 `Thread` 表构建两个索引。[builder](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/CreateTableEnhancedRequest.Builder.html) 参数具有配置两种索引的方法，如注释行 1 和 2 所示。您可以使用索引生成器的 `indexName()` 方法将数据类注释中指定的索引名称与预期的索引类型相关联。

在注释行 3 和 4 之后，此代码将所有表属性配置为最后出现在两个索引中。有关[属性投影](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/LSI.html#LSI.Projections)的更多信息，请参阅《Amazon DynamoDB 开发人员指南》**。

```
    public static void createMessageThreadTable(DynamoDbTable<MessageThread> messageThreadDynamoDbTable, DynamoDbClient dynamoDbClient) {
        messageThreadDynamoDbTable.createTable(b -> b
                // 1. Generate the GSI.
                .globalSecondaryIndices(gsi -> gsi.indexName("SubjectLastPostedDateIndex")
                        // 3. Populate the GSI with all attributes.
                        .projection(p -> p
                                .projectionType(ProjectionType.ALL))
                )
                // 2. Generate the LSI.
                .localSecondaryIndices(lsi -> lsi.indexName("ForumLastPostedDateIndex")
                        // 4. Populate the LSI with all attributes.
                        .projection(p -> p
                                .projectionType(ProjectionType.ALL))
                )
        );
```

## 使用索引查询
<a name="ddb-en-client-use-secindex-query"></a>

以下示例查询本地二级索引 *ForumLastPostedDateIndex*。

在注释行 2 之后，您将创建一个调用 [DynamoDbIndex.query ()](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbIndex.html#query(java.util.function.Consumer)) 方法时所需的[QueryConditional](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/QueryConditional.html)对象。

在注释行 3 之后，通过传入索引名称，即可获得对要查询的索引的引用。在注释行 4 之后，通过传入 `QueryConditional` 对象在索引上调用 `query()` 方法。

您还可以将查询配置为返回三个属性值，如注释行 5 之后所示。如果 `attributesToProject()` 未调用，则查询将返回所有属性值。请注意，指定的属性名称以小写字母开头。这些属性名称与表中使用的属性名称相匹配，不一定与数据类的属性名称相匹配。

在注释行 6 之后，迭代结果、记录查询返回的每个项目，并将其存储在列表中以返回给调用方。

```
public class IndexScanExamples {
    private static Logger logger = LoggerFactory.getLogger(IndexScanExamples.class);

    public static List<MessageThread> queryUsingSecondaryIndices(String lastPostedDate,
                                                                 DynamoDbTable<MessageThread> threadTable) {
        // 1. Log the parameter value.
        logger.info("lastPostedDate value: {}", lastPostedDate);

        // 2. Create a QueryConditional whose sort key value must be greater than or equal to the parameter value.
        QueryConditional queryConditional = QueryConditional.sortGreaterThanOrEqualTo(qc ->
                qc.partitionValue("Forum02").sortValue(lastPostedDate));

        // 3. Specify the index name to query.
        final DynamoDbIndex<MessageThread> forumLastPostedDateIndex = threadTable.index("ForumLastPostedDateIndex");

        // 4. Perform the query using the QueryConditional object.
        final SdkIterable<Page<MessageThread>> pagedResult = forumLastPostedDateIndex.query(q -> q
                .queryConditional(queryConditional)
                // 5. Request three attribute in the results.
                .attributesToProject("forumName", "subject", "lastPostedDateTime"));

        List<MessageThread> collectedItems = new ArrayList<>();
        // 6. Iterate through pages response and sort the items.
        pagedResult.stream().forEach(page -> page.items().stream()
                .sorted(Comparator.comparing(MessageThread::getLastPostedDateTime))
                .forEach(mt -> {
                    // 7. Log the returned items and add the collection to return to the caller.
                    logger.info(mt.toString());
                    collectedItems.add(mt);
                }));
        return collectedItems;
    }
```

在运行查询之前，数据库中存在以下项目。

```
MessageThread{ForumName='Forum01', Subject='Subject01', Message='Message01', LastPostedBy='', LastPostedDateTime='2023.03.28', Views=0, Replies=0, Answered=0, Tags=null}
MessageThread{ForumName='Forum02', Subject='Subject02', Message='Message02', LastPostedBy='', LastPostedDateTime='2023.03.29', Views=0, Replies=0, Answered=0, Tags=null}
MessageThread{ForumName='Forum02', Subject='Subject04', Message='Message04', LastPostedBy='', LastPostedDateTime='2023.03.31', Views=0, Replies=0, Answered=0, Tags=null}
MessageThread{ForumName='Forum02', Subject='Subject08', Message='Message08', LastPostedBy='', LastPostedDateTime='2023.04.04', Views=0, Replies=0, Answered=0, Tags=null}
MessageThread{ForumName='Forum02', Subject='Subject10', Message='Message10', LastPostedBy='', LastPostedDateTime='2023.04.06', Views=0, Replies=0, Answered=0, Tags=null}
MessageThread{ForumName='Forum03', Subject='Subject03', Message='Message03', LastPostedBy='', LastPostedDateTime='2023.03.30', Views=0, Replies=0, Answered=0, Tags=null}
MessageThread{ForumName='Forum03', Subject='Subject06', Message='Message06', LastPostedBy='', LastPostedDateTime='2023.04.02', Views=0, Replies=0, Answered=0, Tags=null}
MessageThread{ForumName='Forum03', Subject='Subject09', Message='Message09', LastPostedBy='', LastPostedDateTime='2023.04.05', Views=0, Replies=0, Answered=0, Tags=null}
MessageThread{ForumName='Forum05', Subject='Subject05', Message='Message05', LastPostedBy='', LastPostedDateTime='2023.04.01', Views=0, Replies=0, Answered=0, Tags=null}
MessageThread{ForumName='Forum07', Subject='Subject07', Message='Message07', LastPostedBy='', LastPostedDateTime='2023.04.03', Views=0, Replies=0, Answered=0, Tags=null}
```

第 1 行和第 6 行的日志语句会生成以下控制台输出。

```
lastPostedDate value: 2023.03.31
MessageThread{ForumName='Forum02', Subject='Subject04', Message='', LastPostedBy='', LastPostedDateTime='2023.03.31', Views=0, Replies=0, Answered=0, Tags=null}
MessageThread{ForumName='Forum02', Subject='Subject08', Message='', LastPostedBy='', LastPostedDateTime='2023.04.04', Views=0, Replies=0, Answered=0, Tags=null}
MessageThread{ForumName='Forum02', Subject='Subject10', Message='', LastPostedBy='', LastPostedDateTime='2023.04.06', Views=0, Replies=0, Answered=0, Tags=null}
```

该查询返回 `forumName` 值为 *Forum02*，`lastPostedDateTime` 值大于或等于 *2023.03.31* 的项目。尽管索引中的 `message` 属性具有值，但结果显示的 `message` 值为空字符串。这是因为在注释行 5 之后，代码未投影消息属性。

# 使用高级映射功能
<a name="ddb-en-client-adv-features"></a>

了解 DynamoDB 增强型客户端 API 中的高级表架构功能。

## 了解表架构类型
<a name="ddb-en-client-adv-features-schm-overview"></a>

`[TableSchema](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/TableSchema.html)` 是 DynamoDB 增强型客户端 API 映射功能的接口。它可以将数据对象映射到地图或从地图映射出来[AttributeValues](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/AttributeValue.html)。`TableSchema` 对象需要知道它所映射的表的结构。此结构信息存储在 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/TableMetadata.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/TableMetadata.html) 对象中。

增强型客户端 API 有以下几种 `TableSchema` 实现。

### 从带注释的类生成的表架构
<a name="ddb-en-client-adv-features-schema-mapped"></a>

从带注释的类构建 `TableSchema` 的操作成本适中，因此我们建议在应用程序启动时执行一次。

 [ BeanTableSchema ](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/mapper/BeanTableSchema.html)   
此实现是基于 Bean 类的属性和注释构建的。[入门部分](ddb-en-client-gs-tableschema.md#ddb-en-client-gs-tableschema-anno-bean)演示了这种方法的示例。  
如果 `BeanTableSchema` 的行为不符合您的预期，请为 `software.amazon.awssdk.enhanced.dynamodb.beans` 启用调试日志记录。

[ImmutableTableSchema](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/mapper/ImmutableTableSchema.html)  
此实现基于不可变的数据类构建。[使用不可变数据类](ddb-en-client-use-immut.md)部分介绍了此方法。

### 使用生成器生成的表架构
<a name="ddb-en-client-adv-features-schema-static"></a>

以下 `TableSchema` 是使用生成器根据代码构建的。这种方法比使用带注释的数据类的方法更便宜。构建器方法避免使用注释，并且不需要 JavaBean 命名标准。

[StaticTableSchema](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/mapper/StaticTableSchema.html)  
此实现是为可变数据类构建的。本指南的入门部分演示了如何[使用生成器生成 `StaticTableSchema`](ddb-en-client-gs-tableschema.md#ddb-en-client-gs-tableschema-builder)。

[StaticImmutableTableSchema](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/mapper/StaticImmutableTableSchema.html)  
与 `StaticTableSchema` 构建方式类似，您使用[生成器](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/mapper/StaticImmutableTableSchema.html)生成这种类型的 `TableSchema` 实现，以用于不可变的数据类。

### 适用于无固定架构数据的表架构
<a name="ddb-en-client-adv-features-schema-document"></a>

[DocumentTableSchema](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/document/DocumentTableSchema.html)  
与其他 `TableSchema` 实现不同，您无需为 `DocumentTableSchema` 实例定义属性。通常，您只需指定主键和属性转换器提供程序。`EnhancedDocument` 实例将提供您根据单个元素或 JSON 字符串构建的属性。

# 明确包含或排除属性
<a name="ddb-en-client-adv-features-inex-attr"></a>

DynamoDB 增强型客户端 API 提供了注释，可将数据类属性排除在表的属性之外。通过 API，您还可以使用与数据类属性名称不同的属性名称。

## 排除属性
<a name="ddb-en-client-adv-features-inex-attr-ex"></a>

要忽略不应映射到 DynamoDB 表的属性，请使用 `@DynamoDbIgnore` 注释标记该属性。

```
private String internalKey;

@DynamoDbIgnore
public String getInternalKey() { return this.internalKey; }
public void setInternalKey(String internalKey) { this.internalKey = internalKey;}
```

## 包含属性
<a name="ddb-en-client-adv-features-inex-attr-in"></a>

要更改 DynamoDB 表中使用的属性的名称，请使用 `@DynamoDbAttribute` 注释标记该属性并提供其他名称。

```
private String internalKey;

@DynamoDbAttribute("renamedInternalKey")
public String getInternalKey() { return this.internalKey; }
public void setInternalKey(String internalKey) { this.internalKey = internalKey;}
```

# 控制属性转换
<a name="ddb-en-client-adv-features-conversion"></a>

默认情况下，表架构通过 `[AttributeConverterProvider](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/AttributeConverterProvider.html)` 接口的默认实现为许多常见的 Java 类型提供转换器。您可以使用自定义 `AttributeConverterProvider` 实现来更改整体默认行为。您还可以更改单个属性的转换器。

有关可用转换器的列表，请参阅[AttributeConverter](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/AttributeConverter.html)接口 Java 文档。

## 提供自定义属性转换器提供程序
<a name="ddb-en-client-adv-features-conversion-prov"></a>

您可以通过 `@DynamoDbBean` `(converterProviders = {…})` 注释提供单个 `AttributeConverterProvider` 或一个有序的 `AttributeConverterProvider` 链。任何自定义 `AttributeConverterProvider` 都必须扩展 `AttributeConverterProvider` 接口。

请注意，如果您提供自己的属性转换器提供程序链，则将覆盖默认的转换器提供程序 `DefaultAttributeConverterProvider`。如果您要使用 `DefaultAttributeConverterProvider` 的功能，必须将其包含在链中。

也可以用空 `{}` 数组对 Bean 进行注释。这将禁用任何属性转换器提供程序，包括默认提供程序。在这种情况下，所有要映射的属性都必须有自己的属性转换器。

以下代码段显示了单个转换器提供程序。

```
@DynamoDbBean(converterProviders = ConverterProvider1.class)
public class Customer {

}
```

以下代码段显示了转换器提供程序链的用法。由于 SDK 默认转换器排在最后，因此它的优先级最低。

```
@DynamoDbBean(converterProviders = {
   ConverterProvider1.class, 
   ConverterProvider2.class,
   DefaultAttributeConverterProvider.class})
public class Customer {

}
```

静态表架构生成器有一种工作方式与此相同的 `attributeConverterProviders()` 方法。如以下代码段所示。

```
private static final StaticTableSchema<Customer> CUSTOMER_TABLE_SCHEMA =
  StaticTableSchema.builder(Customer.class)
    .newItemSupplier(Customer::new)
    .addAttribute(String.class, a -> a.name("name")
                                     a.getter(Customer::getName)
                                     a.setter(Customer::setName))
    .attributeConverterProviders(converterProvider1, converterProvider2)
    .build();
```

## 覆盖单个属性的映射
<a name="ddb-en-client-adv-features-conversion-single"></a>

要覆盖单个属性的映射方式，请为该属性提供一个 `AttributeConverter`。此添加会覆盖表架构中由 `AttributeConverterProviders` 提供的任何转换器。这将仅为该属性添加一个自定义转换器。除非为其他属性明确指定该转换器，否则其他属性即使类型相同，也不会使用该转换器。

`@DynamoDbConvertedBy` 注释用于指定自定义 `AttributeConverter` 类，如以下代码段所示。

```
@DynamoDbBean
public class Customer {
    private String name;

    @DynamoDbConvertedBy(CustomAttributeConverter.class)
    public String getName() { return this.name; }
    public void setName(String name) { this.name = name;}
}
```

静态架构的生成器具有等效的属性生成器 `attributeConverter()` 方法。此方法采用 `AttributeConverter` 的实例，如下所示。

```
private static final StaticTableSchema<Customer> CUSTOMER_TABLE_SCHEMA =
  StaticTableSchema.builder(Customer.class)
    .newItemSupplier(Customer::new)
    .addAttribute(String.class, a -> a.name("name")
                                     a.getter(Customer::getName)
                                     a.setter(Customer::setName)
                                     a.attributeConverter(customAttributeConverter))
    .build();
```

## 示例
<a name="ddb-en-client-adv-features-conversion-example"></a>

此示例演示一个为 [https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/HttpCookie.html](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/HttpCookie.html) 对象提供属性转换器的 `AttributeConverterProvider` 实现。

以下 `SimpleUser` 类包含一个名为 `lastUsedCookie` 的属性，该属性是 `HttpCookie` 的一个实例。

`@DynamoDbBean` 注释的参数列出了提供转换器的两个 `AttributeConverterProvider` 类。

------
#### [ Class with annotations ]

```
    @DynamoDbBean(converterProviders = {CookieConverterProvider.class, DefaultAttributeConverterProvider.class})
    public static final class SimpleUser {
        private String name;
        private HttpCookie lastUsedCookie;

        @DynamoDbPartitionKey
        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public HttpCookie getLastUsedCookie() {
            return lastUsedCookie;
        }

        public void setLastUsedCookie(HttpCookie lastUsedCookie) {
            this.lastUsedCookie = lastUsedCookie;
        }
```

------
#### [ Static table schema ]

```
    private static final TableSchema<SimpleUser> SIMPLE_USER_TABLE_SCHEMA =
            TableSchema.builder(SimpleUser.class)
                    .newItemSupplier(SimpleUser::new)
                    .attributeConverterProviders(CookieConverterProvider.create(), AttributeConverterProvider.defaultProvider())
                    .addAttribute(String.class, a -> a.name("name")
                            .setter(SimpleUser::setName)
                            .getter(SimpleUser::getName)
                            .tags(StaticAttributeTags.primaryPartitionKey()))
                    .addAttribute(HttpCookie.class, a -> a.name("lastUsedCookie")
                            .setter(SimpleUser::setLastUsedCookie)
                            .getter(SimpleUser::getLastUsedCookie))
                    .build();
```

------

以下示例中的 `CookieConverterProvider` 提供了 `HttpCookeConverter` 的一个实例。

```
    public static final class CookieConverterProvider implements AttributeConverterProvider {
        private final Map<EnhancedType<?>, AttributeConverter<?>> converterCache = ImmutableMap.of(
                // 1. Add HttpCookieConverter to the internal cache.
                EnhancedType.of(HttpCookie.class), new HttpCookieConverter());

        public static CookieConverterProvider create() {
            return new CookieConverterProvider();
        }

        // The SDK calls this method to find out if the provider contains a AttributeConverter instance
        // for the EnhancedType<T> argument.
        @SuppressWarnings("unchecked")
        @Override
        public <T> AttributeConverter<T> converterFor(EnhancedType<T> enhancedType) {
            return (AttributeConverter<T>) converterCache.get(enhancedType);
        }
    }
```

### 代码转换
<a name="ddb-en-client-adv-features-conversion-example-code"></a>

在以下 `HttpCookieConverter` 类的 `transformFrom()` 方法中，代码接收一个 `HttpCookie` 实例并将其转换为 DynamoDB 映射，并将该映射作为属性存储。

`transformTo()` 方法接收 DynamoDB 映射参数，然后调用需要名称和值的 `HttpCookie` 构造函数。

```
    public static final class HttpCookieConverter implements AttributeConverter<HttpCookie> {

        @Override
        public AttributeValue transformFrom(HttpCookie httpCookie) {

            return AttributeValue.fromM(
            Map.of ("cookieName", AttributeValue.fromS(httpCookie.getName()),
                    "cookieValue", AttributeValue.fromS(httpCookie.getValue()))
            );
        }

        @Override
        public HttpCookie transformTo(AttributeValue attributeValue) {
            Map<String, AttributeValue> map = attributeValue.m();
            return new HttpCookie(
                    map.get("cookieName").s(),
                    map.get("cookieValue").s());
        }

        @Override
        public EnhancedType<HttpCookie> type() {
            return EnhancedType.of(HttpCookie.class);
        }

        @Override
        public AttributeValueType attributeValueType() {
            return AttributeValueType.M;
        }
    }
```

# 更改属性的更新行为
<a name="ddb-en-client-adv-features-upd-behavior"></a>

在执行*更新* 操作时，您可以自定义各个属性的更新行为。[DynamoDB 增强型客户端 API 中的一些更新操作示例如下 [updateItem](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbTable.html#updateItem(T)) () 和 ()。transactWriteItems](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedClient.html#transactWriteItems(java.util.function.Consumer))

例如，假设您要在记录中存储 *created on* 时间戳。但是，您希望仅在数据库中没有该属性的现有值时才写入其值。在这种情况下，您可以使用 `[WRITE\$1IF\$1NOT\$1EXISTS](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/mapper/UpdateBehavior.html#WRITE_IF_NOT_EXISTS)` 更新行为。

以下示例展示了将行为添加到 `createdOn` 属性的注释。

```
@DynamoDbBean
public class Customer extends GenericRecord {
    private String id;
    private Instant createdOn;

    @DynamoDbPartitionKey
    public String getId() { return this.id; }
    public void setId(String id) { this.name = id; }

    @DynamoDbUpdateBehavior(UpdateBehavior.WRITE_IF_NOT_EXISTS)
    public Instant getCreatedOn() { return this.createdOn; }    
    public void setCreatedOn(Instant createdOn) { this.createdOn = createdOn; }
}
```

在构建静态表架构时，您可以声明相同的更新行为，如以下示例的注释行 1 之后所示。

```
static final TableSchema<Customer> CUSTOMER_TABLE_SCHEMA =
     TableSchema.builder(Customer.class)
       .newItemSupplier(Customer::new)
       .addAttribute(String.class, a -> a.name("id")
                                         .getter(Customer::getId)
                                         .setter(Customer::setId)
                                         .tags(StaticAttributeTags.primaryPartitionKey()))
       .addAttribute(Instant.class, a -> a.name("createdOn")
                                          .getter(Customer::getCreatedOn)
                                          .setter(Customer::setCreatedOn)
                                          // 1. Add an UpdateBehavior.
                                          .tags(StaticAttributeTags.updateBehavior(UpdateBehavior.WRITE_IF_NOT_EXISTS)))
       .build();
```

# 扁平化其他类的属性
<a name="ddb-en-client-adv-features-flatmap"></a>

如果表的属性通过继承或组合分布在多个不同的 Java 类中，则 DynamoDB 增强型客户端 API 支持将这些属性扁平化为一个类。

## 使用继承
<a name="ddb-en-client-adv-features-flatmap-inheritance"></a>

如果您的类使用继承，请使用以下方法来扁平化层次结构。

### 使用带注释的 Bean
<a name="ddb-en-client-adv-features-flatmap-inheritance-anno"></a>

对于注释方法，两个类都必须带有 `@DynamoDbBean` 注释，并且一个类必须带有一个或多个主键注释。

以下是具有继承关系的数据类的示例。

------
#### [ Standard data class ]

```
@DynamoDbBean
public class Customer extends GenericRecord {
    private String name;

    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
}

@DynamoDbBean
public abstract class GenericRecord {
    private String id;
    private String createdDate;

    @DynamoDbPartitionKey
    public String getId() { return id; }
    public void setId(String id) { this.id = id; }

    public String getCreatedDate() { return createdDate; }
    public void setCreatedDate(String createdDate) { this.createdDate = createdDate; }
}
```

------
#### [ Lombok ]

Lombok 的 [`onMethod` 选项](https://projectlombok.org/features/experimental/onX)将基于属性的 DynamoDB 注释（例如 `@DynamoDbPartitionKey`）复制到生成的代码中。

```
@DynamoDbBean
@Data
@ToString(callSuper = true)
public class Customer extends GenericRecord {
    private String name;
}

@Data
@DynamoDbBean
public abstract class GenericRecord {
    @Getter(onMethod_=@DynamoDbPartitionKey)
    private String id;
    private String createdDate;
}
```

------

### 使用静态架构
<a name="ddb-en-client-adv-features-flatmap-inheritance-static"></a>

对于静态架构方法，使用生成器的 `extend()` 方法将父类的属性折叠到子类上。如以下示例的注释行 1 之后所示。

```
        StaticTableSchema<org.example.tests.model.inheritance.stat.GenericRecord> GENERIC_RECORD_SCHEMA =
                StaticTableSchema.builder(org.example.tests.model.inheritance.stat.GenericRecord.class)
                        // The partition key will be inherited by the top level mapper.
                        .addAttribute(String.class, a -> a.name("id")
                                .getter(org.example.tests.model.inheritance.stat.GenericRecord::getId)
                                .setter(org.example.tests.model.inheritance.stat.GenericRecord::setId)
                                .tags(primaryPartitionKey()))
                        .addAttribute(String.class, a -> a.name("created_date")
                                .getter(org.example.tests.model.inheritance.stat.GenericRecord::getCreatedDate)
                                .setter(org.example.tests.model.inheritance.stat.GenericRecord::setCreatedDate))
                        .build();

        StaticTableSchema<org.example.tests.model.inheritance.stat.Customer> CUSTOMER_SCHEMA =
                StaticTableSchema.builder(org.example.tests.model.inheritance.stat.Customer.class)
                        .newItemSupplier(org.example.tests.model.inheritance.stat.Customer::new)
                        .addAttribute(String.class, a -> a.name("name")
                                .getter(org.example.tests.model.inheritance.stat.Customer::getName)
                                .setter(org.example.tests.model.inheritance.stat.Customer::setName))
                        // 1. Use the extend() method to collapse the parent attributes onto the child class.
                        .extend(GENERIC_RECORD_SCHEMA)     // All the attributes of the GenericRecord schema are added to Customer.
                        .build();
```

前面的静态架构示例使用以下数据类。由于映射是在构建静态表架构时定义的，因此数据类不需要注释。

#### 数据类
<a name="gunk"></a>

------
#### [ Standard data class ]

```
public class Customer extends GenericRecord {
    private String name;

    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
}


public abstract class GenericRecord {
    private String id;
    private String createdDate;

    public String getId() { return id; }
    public void setId(String id) { this.id = id; }

    public String getCreatedDate() { return createdDate; }
    public void setCreatedDate(String createdDate) { this.createdDate = createdDate; }
```

------
#### [ Lombok ]

```
@Data
@ToString(callSuper = true)
public class Customer extends GenericRecord{
    private String name;
}

@Data
public abstract class GenericRecord {
    private String id;
    private String createdDate;
}
```

------

## 使用组合
<a name="ddb-en-client-adv-features-flatmap-comp"></a>

如果您的类使用组合，请使用以下方法来扁平化层次结构。

### 使用带注释的 Bean
<a name="ddb-en-client-adv-features-flatmap-comp-anno"></a>

使用 `@DynamoDbFlatten` 注释将所含的类扁平化。

以下数据类示例使用 `@DynamoDbFlatten` 注释将所含的 `GenericRecord` 类的所有属性有效添加到 `Customer` 类中。

------
#### [ Standard data class ]

```
@DynamoDbBean
public class Customer {
    private String name;
    private GenericRecord record;

    public String getName() { return this.name; }
    public void setName(String name) { this.name = name; }

    @DynamoDbFlatten
    public GenericRecord getRecord() { return this.record; }
    public void setRecord(GenericRecord record) { this.record = record; }

@DynamoDbBean
public class GenericRecord {
    private String id;
    private String createdDate;

    @DynamoDbPartitionKey
    public String getId() { return this.id; }
    public void setId(String id) { this.id = id; }

    public String getCreatedDate() { return this.createdDate; }
    public void setCreatedDate(String createdDate) { this.createdDate = createdDate; }
}
```

------
#### [ Lombok ]

```
@Data
@DynamoDbBean
public class Customer {
    private String name;
    @Getter(onMethod_=@DynamoDbFlatten)
    private GenericRecord record;
}

@Data
@DynamoDbBean
public class GenericRecord {
    @Getter(onMethod_=@DynamoDbPartitionKey)
    private String id;
    private String createdDate;
}
```

------

您可以根据需要，使用扁平化注释对任意数量的符合条件的不同类进行扁平化。以下限制适用：
+ 所有属性名称在扁平化后必须是唯一的。
+ 分区键、排序键或表名称不得超过一个。

### 使用静态架构
<a name="ddb-en-client-adv-features-flatmap-comp-static"></a>

构建静态表架构时，请使用生成器的 `flatten()` 方法。您还可以提供用于识别所含类的 getter 和 setter 方法。

```
        StaticTableSchema<GenericRecord> GENERIC_RECORD_SCHEMA =
                StaticTableSchema.builder(GenericRecord.class)
                        .newItemSupplier(GenericRecord::new)
                        .addAttribute(String.class, a -> a.name("id")
                                .getter(GenericRecord::getId)
                                .setter(GenericRecord::setId)
                                .tags(primaryPartitionKey()))
                        .addAttribute(String.class, a -> a.name("created_date")
                                .getter(GenericRecord::getCreatedDate)
                                .setter(GenericRecord::setCreatedDate))
                        .build();

        StaticTableSchema<Customer> CUSTOMER_SCHEMA =
                StaticTableSchema.builder(Customer.class)
                        .newItemSupplier(Customer::new)
                        .addAttribute(String.class, a -> a.name("name")
                                .getter(Customer::getName)
                                .setter(Customer::setName))
                        // Because we are flattening a component object, we supply a getter and setter so the
                        // mapper knows how to access it.
                        .flatten(GENERIC_RECORD_SCHEMA, Customer::getRecord, Customer::setRecord)
                        .build();
```

前面的静态架构示例使用以下数据类。

#### 数据类
<a name="ddb-en-client-adv-features-flatmap-comp-static-supporting"></a>

------
#### [ Standard data class ]

```
public class Customer {
    private String name;
    private GenericRecord record;

    public String getName() { return this.name; }
    public void setName(String name) { this.name = name; }

    public GenericRecord getRecord() { return this.record; }
    public void setRecord(GenericRecord record) { this.record = record; }

public class GenericRecord {
    private String id;
    private String createdDate;

    public String getId() { return this.id; }
    public void setId(String id) { this.id = id; }

    public String getCreatedDate() { return this.createdDate; }
    public void setCreatedDate(String createdDate) { this.createdDate = createdDate; }
}
```

------
#### [ Lombok ]

```
@Data
public class Customer {
    private String name;
    private GenericRecord record;
}

@Data
public class GenericRecord {
    private String id;
    private String createdDate;
}
```

------

您可以根据需要，使用生成器模式对任意数量的符合条件的不同类进行扁平化。

## 对其他代码的影响
<a name="ddb-en-client-adv-features-flatmap-compare"></a>

当您使用 `@DynamoDbFlatten` 属性（或 `flatten()` 生成器方法）时，DynamoDB 中的项目将针对组合成的对象的每个属性包含一个属性。它还包括进行组合的对象的属性。

相反，如果您使用组合成的类对数据类进行注释但不使用 `@DynamoDbFlatten`，则该项目将与组合成的对象一起保存为单个属性。

例如，我们可以比较[使用组合的扁平化示例](#ddb-en-client-adv-features-flatmap-comp-anno)中显示的 `Customer` 类在对 `record` 属性进行扁平化和不进行扁平化时的区别。您可以使用 JSON 可视化此区别，如下表所示。


****  

| 进行扁平化 | 不进行扁平化 | 
| --- | --- | 
| 3 个属性 | 2 个属性 | 
|  <pre>{<br />  "id": "1",<br />  "createdDate": "today",<br />  "name": "my name"<br />}</pre>  |  <pre>{<br />  "id": "1",<br />  "record": {<br />      "createdDate": "today",<br />      "name": "my name"<br />  }<br />}</pre>  | 

如果您有其他代码访问 DynamoDB 表并希望找到某些属性，则此区别就变得很重要。

# 处理属性值为 bean、map、list 和 set 的对象
<a name="ddb-en-client-adv-features-nested"></a>

一个 bean 定义（例如下面所示的 `Person` 类）可能会定义一些属性，而这些属性所引用的类型本身还包含更多的属性。例如，在 `Person` 类中，`mainAddress` 是一个属性，该属性引用定义了额外值属性的 `Address` bean。`addresses` 引用一个 Java map，其元素引用 `Address` bean。这些复杂类型可以看作是包含简单属性的容器，在 DynamoDB 环境中，您使用这些容器是为了获取其所包含的实际数据值。

DynamoDB 将嵌套元素（例如 map、list 或 bean）的值属性称为*嵌套属性*。[Amazon DynamoDB 开发人员指南](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes)将 Java 中的 map、list 或 bean 对象保存后的形式称为*文档类型*。在 Java 中，用于其数据值的简单属性在 DynamoDB 中称为*标量类型*。set 包含多个相同类型的标量元素，所以称为*集类型*。

重要的是要知道，DynamoDB 增强型客户端 API 在保存时会将 bean 属性转换为 DynamoDB map 文档类型。

## `Person` 类
<a name="ddb-en-client-adv-features-nested-person"></a>

```
@DynamoDbBean
public class Person {
    private Integer id;
    private String firstName;
    private String lastName;
    private Integer age;
    private Address mainAddress;
    private Map<String, Address> addresses;
    private List<PhoneNumber> phoneNumbers;
    private Set<String> hobbies;

    @DynamoDbPartitionKey
    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Address getMainAddress() {
        return mainAddress;
    }

    public void setMainAddress(Address mainAddress) {
        this.mainAddress = mainAddress;
    }

    public Map<String, Address> getAddresses() {
        return addresses;
    }

    public void setAddresses(Map<String, Address> addresses) {
        this.addresses = addresses;
    }

    public List<PhoneNumber> getPhoneNumbers() {
        return phoneNumbers;
    }

    public void setPhoneNumbers(List<PhoneNumber> phoneNumbers) {
        this.phoneNumbers = phoneNumbers;
    }

    public Set<String> getHobbies() {
        return hobbies;
    }

    public void setHobbies(Set<String> hobbies) {
        this.hobbies = hobbies;
    }

    @Override
    public String toString() {
        return "Person{" +
               "addresses=" + addresses +
               ", id=" + id +
               ", firstName='" + firstName + '\'' +
               ", lastName='" + lastName + '\'' +
               ", age=" + age +
               ", mainAddress=" + mainAddress +
               ", phoneNumbers=" + phoneNumbers +
               ", hobbies=" + hobbies +
               '}';
    }
}
```

## `Address` 类
<a name="ddb-en-client-adv-features-nested-address"></a>

```
@DynamoDbBean
public class Address {
    private String street;
    private String city;
    private String state;
    private String zipCode;

    public Address() {
    }

    public String getStreet() {
        return this.street;
    }

    public String getCity() {
        return this.city;
    }

    public String getState() {
        return this.state;
    }

    public String getZipCode() {
        return this.zipCode;
    }

    public void setStreet(String street) {
        this.street = street;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public void setState(String state) {
        this.state = state;
    }

    public void setZipCode(String zipCode) {
        this.zipCode = zipCode;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Address address = (Address) o;
        return Objects.equals(street, address.street) && Objects.equals(city, address.city) && Objects.equals(state, address.state) && Objects.equals(zipCode, address.zipCode);
    }

    @Override
    public int hashCode() {
        return Objects.hash(street, city, state, zipCode);
    }

    @Override
    public String toString() {
        return "Address{" +
                "street='" + street + '\'' +
                ", city='" + city + '\'' +
                ", state='" + state + '\'' +
                ", zipCode='" + zipCode + '\'' +
                '}';
    }
}
```

## `PhoneNumber` 类
<a name="ddb-en-client-adv-features-nested-phonenumber"></a>

```
@DynamoDbBean
public class PhoneNumber {
    String type;
    String number;

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public String getNumber() {
        return number;
    }

    public void setNumber(String number) {
        this.number = number;
    }

    @Override
    public String toString() {
        return "PhoneNumber{" +
                "type='" + type + '\'' +
                ", number='" + number + '\'' +
                '}';
    }
}
```

## 保存复杂类型
<a name="ddb-en-client-adv-features-nested-mapping"></a>

### 使用带注释的数据类
<a name="ddb-en-client-adv-features-nested-map-anno"></a>

通过注释来保存自定义类的嵌套属性。前面显示的 `Address` 类和 `PhoneNumber` 类仅使用 `@DynamoDbBean` 注释进行注释。当 DynamoDB 增强型客户端 API 使用以下代码段为 `Person` 类生成表架构时，API 会发现 `Address` 和 `PhoneNumber` 类的使用，并生成相应的映射以与 DynamoDB 结合使用。

```
TableSchema<Person> personTableSchema = TableSchema.fromBean(Person.class);
```

### 在生成器中使用抽象架构
<a name="ddb-en-client-adv-features-nested-map-builder"></a>

另一种方法是为每个嵌套 bean 类使用静态表架构生成器，如以下代码所示。

`Address` 和 `PhoneNumber` 类的表架构是抽象的，不能与 DynamoDB 表一起使用。这是因为它们缺少主键的定义。但是，它们在 `Person` 类的表架构中用作嵌套架构。

在注释行 1 和 2 之后的 `PERSON_TABLE_SCHEMA` 定义中，显示了使用抽象表架构的代码。在 `EnhanceType.documentOf(...)` 方法中使用 `documentOf` 并不表示该方法将返回增强型文档 API 的 `EnhancedDocument` 类型。在此上下文中，`documentOf(...)` 方法将返回一个对象，该对象知道如何使用表架构参数，在其类参数和 DynamoDB 表属性之间进行映射。

#### 静态架构代码
<a name="ddb-en-client-adv-features-nested-map-builder-code"></a>

```
    // Abstract table schema that cannot be used to work with a DynamoDB table,
    // but can be used as a nested schema.
    public static final TableSchema<Address> TABLE_SCHEMA_ADDRESS = TableSchema.builder(Address.class)
        .newItemSupplier(Address::new)
        .addAttribute(String.class, a -> a.name("street")
            .getter(Address::getStreet)
            .setter(Address::setStreet))
        .addAttribute(String.class, a -> a.name("city")
            .getter(Address::getCity)
            .setter(Address::setCity))
        .addAttribute(String.class, a -> a.name("zipcode")
            .getter(Address::getZipCode)
            .setter(Address::setZipCode))
        .addAttribute(String.class, a -> a.name("state")
            .getter(Address::getState)
            .setter(Address::setState))
        .build();

    // Abstract table schema that cannot be used to work with a DynamoDB table,
    // but can be used as a nested schema.
    public static final TableSchema<PhoneNumber> TABLE_SCHEMA_PHONENUMBER = TableSchema.builder(PhoneNumber.class)
        .newItemSupplier(PhoneNumber::new)
        .addAttribute(String.class, a -> a.name("type")
            .getter(PhoneNumber::getType)
            .setter(PhoneNumber::setType))
        .addAttribute(String.class, a -> a.name("number")
            .getter(PhoneNumber::getNumber)
            .setter(PhoneNumber::setNumber))
        .build();

    // A static table schema that can be used with a DynamoDB table.
    // The table schema contains two nested schemas that are used to perform mapping to/from DynamoDB.
    public static final TableSchema<Person> PERSON_TABLE_SCHEMA =
        TableSchema.builder(Person.class)
            .newItemSupplier(Person::new)
            .addAttribute(Integer.class, a -> a.name("id")
                .getter(Person::getId)
                .setter(Person::setId)
                .addTag(StaticAttributeTags.primaryPartitionKey()))
            .addAttribute(String.class, a -> a.name("firstName")
                .getter(Person::getFirstName)
                .setter(Person::setFirstName))
            .addAttribute(String.class, a -> a.name("lastName")
                .getter(Person::getLastName)
                .setter(Person::setLastName))
            .addAttribute(Integer.class, a -> a.name("age")
                .getter(Person::getAge)
                .setter(Person::setAge))
            .addAttribute(EnhancedType.documentOf(Address.class, TABLE_SCHEMA_ADDRESS), a -> a.name("mainAddress")
                .getter(Person::getMainAddress)
                .setter(Person::setMainAddress))
            .addAttribute(EnhancedType.listOf(String.class), a -> a.name("hobbies")
                .getter(Person::getHobbies)
                .setter(Person::setHobbies))
            .addAttribute(EnhancedType.mapOf(
                EnhancedType.of(String.class),
                // 1. Use mapping functionality of the Address table schema.
                EnhancedType.documentOf(Address.class, TABLE_SCHEMA_ADDRESS)), a -> a.name("addresses")
                .getter(Person::getAddresses)
                .setter(Person::setAddresses))
            .addAttribute(EnhancedType.listOf(
                // 2. Use mapping functionality of the PhoneNumber table schema.
                EnhancedType.documentOf(PhoneNumber.class, TABLE_SCHEMA_PHONENUMBER)), a -> a.name("phoneNumbers")
                .getter(Person::getPhoneNumbers)
                .setter(Person::setPhoneNumbers))
            .build();
```

## 复杂类型的项目属性
<a name="ddb-en-client-adv-features-nested-projection"></a>

对于 `query()` 和 `scan()` 方法，您可以使用 `addNestedAttributeToProject()` 和 `attributesToProject()` 之类的方法调用来指定要在结果中返回哪些属性。在发送请求之前，DynamoDB 增强型客户端 API 会将 Java 方法调用参数转换为[投影表达式](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.ProjectionExpressions.html)。

以下示例在 `Person` 表中填充两个项目，然后执行三个扫描操作。

第一个扫描访问表中的所有项目，以便将结果与其他扫描操作进行比较。

第二个扫描使用 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/ScanEnhancedRequest.Builder.html#addNestedAttributeToProject(software.amazon.awssdk.enhanced.dynamodb.NestedAttributeName)](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/ScanEnhancedRequest.Builder.html#addNestedAttributeToProject(software.amazon.awssdk.enhanced.dynamodb.NestedAttributeName)) 生成器方法仅返回 `street` 属性值。

第三个扫描操作使用 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/ScanEnhancedRequest.Builder.html#attributesToProject(java.lang.String...)](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/ScanEnhancedRequest.Builder.html#attributesToProject(java.lang.String...)) 生成器方法返回第一级属性 `hobbies` 的数据。`hobbies` 的属性类型是列表。要访问单个列表项目，请对列表执行 `get()` 操作。

```
        personDynamoDbTable = getDynamoDbEnhancedClient().table("Person", PERSON_TABLE_SCHEMA);
        PersonUtils.createPersonTable(personDynamoDbTable, getDynamoDbClient());
        // Use a utility class to add items to the Person table.
        List<Person> personList = PersonUtils.getItemsForCount(2);
        // This utility method performs a put against DynamoDB to save the instances in the list argument.
        PersonUtils.putCollection(getDynamoDbEnhancedClient(), personList, personDynamoDbTable);

        // The first scan logs all items in the table to compare to the results of the subsequent scans.
        final PageIterable<Person> allItems = personDynamoDbTable.scan();
        allItems.items().forEach(p ->
                // 1. Log what is in the table.
                logger.info(p.toString()));

        // Scan for nested attributes.
        PageIterable<Person> streetScanResult = personDynamoDbTable.scan(b -> b
                // Use the 'addNestedAttributeToProject()' or 'addNestedAttributesToProject()' to access data nested in maps in DynamoDB.
                .addNestedAttributeToProject(
                        NestedAttributeName.create("addresses", "work", "street")
                ));

        streetScanResult.items().forEach(p ->
                //2. Log the results of requesting nested attributes.
                logger.info(p.toString()));

        // Scan for a top-level list attribute.
        PageIterable<Person> hobbiesScanResult = personDynamoDbTable.scan(b -> b
                // Use the 'attributesToProject()' method to access first-level attributes.
                .attributesToProject("hobbies"));

        hobbiesScanResult.items().forEach((p) -> {
            // 3. Log the results of the request for the 'hobbies' attribute.
            logger.info(p.toString());
            // To access an item in a list, first get the parent attribute, 'hobbies', then access items in the list.
            String hobby = p.getHobbies().get(1);
            // 4. Log an item in the list.
            logger.info(hobby);
        });
```

```
// Logged results from comment line 1.
Person{id=2, firstName='first name 2', lastName='last name 2', age=11, addresses={work=Address{street='street 21', city='city 21', state='state 21', zipCode='33333'}, home=Address{street='street 2', city='city 2', state='state 2', zipCode='22222'}}, phoneNumbers=[PhoneNumber{type='home', number='222-222-2222'}, PhoneNumber{type='work', number='333-333-3333'}], hobbies=[hobby 2, hobby 21]}
Person{id=1, firstName='first name 1', lastName='last name 1', age=11, addresses={work=Address{street='street 11', city='city 11', state='state 11', zipCode='22222'}, home=Address{street='street 1', city='city 1', state='state 1', zipCode='11111'}}, phoneNumbers=[PhoneNumber{type='home', number='111-111-1111'}, PhoneNumber{type='work', number='222-222-2222'}], hobbies=[hobby 1, hobby 11]}

// Logged results from comment line 2.
Person{id=null, firstName='null', lastName='null', age=null, addresses={work=Address{street='street 21', city='null', state='null', zipCode='null'}}, phoneNumbers=null, hobbies=null}
Person{id=null, firstName='null', lastName='null', age=null, addresses={work=Address{street='street 11', city='null', state='null', zipCode='null'}}, phoneNumbers=null, hobbies=null}

// Logged results from comment lines 3 and 4.
Person{id=null, firstName='null', lastName='null', age=null, addresses=null, phoneNumbers=null, hobbies=[hobby 2, hobby 21]}
hobby 21
Person{id=null, firstName='null', lastName='null', age=null, addresses=null, phoneNumbers=null, hobbies=[hobby 1, hobby 11]}
hobby 11
```

**注意**  
如果 `attributesToProject()` 方法遵循任何其他用于添加要投影的属性的生成器方法，则提供给 `attributesToProject()` 的属性名称列表将替换所有其他属性名称。  
在以下代码段中，使用 `ScanEnhancedRequest` 实例执行的扫描仅返回业余爱好数据。  

```
ScanEnhancedRequest lastOverwrites = ScanEnhancedRequest.builder()
        .addNestedAttributeToProject(
                NestedAttributeName.create("addresses", "work", "street"))
        .addAttributeToProject("firstName")
        // If the 'attributesToProject()' method follows other builder methods that add attributes for projection,
        // its list of attributes replace all previous attributes.
        .attributesToProject("hobbies")
        .build();
PageIterable<Person> hobbiesOnlyResult = personDynamoDbTable.scan(lastOverwrites);
hobbiesOnlyResult.items().forEach(p ->
        logger.info(p.toString()));

// Logged results.
Person{id=null, firstName='null', lastName='null', age=null, addresses=null, phoneNumbers=null, hobbies=[hobby 2, hobby 21]}
Person{id=null, firstName='null', lastName='null', age=null, addresses=null, phoneNumbers=null, hobbies=[hobby 1, hobby 11]}
```
以下代码段首先使用 `attributesToProject()` 方法。此排序保留了请求的所有其他属性。  

```
ScanEnhancedRequest attributesPreserved = ScanEnhancedRequest.builder()
        // Use 'attributesToProject()' first so that the method call does not replace all other attributes
        // that you want to project.
        .attributesToProject("firstName")
        .addNestedAttributeToProject(
                NestedAttributeName.create("addresses", "work", "street"))
        .addAttributeToProject("hobbies")
        .build();
PageIterable<Person> allAttributesResult = personDynamoDbTable.scan(attributesPreserved);
allAttributesResult.items().forEach(p ->
        logger.info(p.toString()));

// Logged results.
Person{id=null, firstName='first name 2', lastName='null', age=null, addresses={work=Address{street='street 21', city='null', state='null', zipCode='null'}}, phoneNumbers=null, hobbies=[hobby 2, hobby 21]}
Person{id=null, firstName='first name 1', lastName='null', age=null, addresses={work=Address{street='street 11', city='null', state='null', zipCode='null'}}, phoneNumbers=null, hobbies=[hobby 1, hobby 11]}
```

## 在表达式中使用复杂类型
<a name="ddb-en-client-adv-features-nested-expressions"></a>

您可以在表达式（例如筛选表达式和条件表达式）中使用复杂类型，通过使用解除引用操作符来访问复杂类型的结构。对于对象和 map，使用 `. (dot)`；对于 list 元素，使用 `[n]`（元素序号加上方括号）。不能直接引用 set 中的单个元素，但可以使用 [`contains` 函数](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html#Expressions.OperatorsAndFunctions.Functions)。

以下示例显示了在扫描操作中使用的两个筛选表达式。筛选表达式为要在结果中显示的项目指定匹配条件。该示例使用前面显示的 `Person`、`Address` 和 `PhoneNumber` 类。

```
    public void scanUsingFilterOfNestedAttr() {
        // The following is a filter expression for an attribute that is a map of Address objects.
        // By using this filter expression, the SDK returns Person objects that have an address
        // with 'mailing' as a key and 'MS2' for a state value.
        Expression addressFilter = Expression.builder()
                .expression("addresses.#type.#field = :value")
                .putExpressionName("#type", "mailing")
                .putExpressionName("#field", "state")
                .putExpressionValue(":value", AttributeValue.builder().s("MS2").build())
                .build();

        PageIterable<Person> addressFilterResults = personDynamoDbTable.scan(rb -> rb.
                filterExpression(addressFilter));
        addressFilterResults.items().stream().forEach(p -> logger.info("Person: {}", p));

        assert addressFilterResults.items().stream().count() == 1;


        // The following is a filter expression for an attribute that is a list of phone numbers.
        // By using this filter expression, the SDK returns Person objects whose second phone number
        // in the list has a type equal to 'cell'.
        Expression phoneFilter = Expression.builder()
                .expression("phoneNumbers[1].#type = :type")
                .putExpressionName("#type", "type")
                .putExpressionValue(":type", AttributeValue.builder().s("cell").build())
                .build();

        PageIterable<Person> phoneFilterResults = personDynamoDbTable.scan(rb -> rb
                .filterExpression(phoneFilter)
                .attributesToProject("id", "firstName", "lastName", "phoneNumbers")
        );

        phoneFilterResults.items().stream().forEach(p -> logger.info("Person: {}", p));

        assert phoneFilterResults.items().stream().count() == 1;
        assert phoneFilterResults.items().stream().findFirst().get().getPhoneNumbers().get(1).getType().equals("cell");
    }
```

### 用于向表中填充数据的辅助方法
<a name="nested-expressions-helper-method"></a>

```
    public static void populateDatabase() {
        Person person1 = new Person();
        person1.setId(1);
        person1.setFirstName("FirstName1");
        person1.setLastName("LastName1");

        Address billingAddr1 = new Address();
        billingAddr1.setState("BS1");
        billingAddr1.setCity("BillingTown1");

        Address mailing1 = new Address();
        mailing1.setState("MS1");
        mailing1.setCity("MailingTown1");

        person1.setAddresses(Map.of("billing", billingAddr1, "mailing", mailing1));

        PhoneNumber pn1_1 = new PhoneNumber();
        pn1_1.setType("work");
        pn1_1.setNumber("111-111-1111");

        PhoneNumber pn1_2 = new PhoneNumber();
        pn1_2.setType("home");
        pn1_2.setNumber("222-222-2222");

        List<PhoneNumber> phoneNumbers1 = List.of(pn1_1, pn1_2);
        person1.setPhoneNumbers(phoneNumbers1);

        personDynamoDbTable.putItem(person1);

        Person person2 = person1;
        person2.setId(2);
        person2.setFirstName("FirstName2");
        person2.setLastName("LastName2");

        Address billingAddress2 = billingAddr1;
        billingAddress2.setCity("BillingTown2");
        billingAddress2.setState("BS2");

        Address mailing2 = mailing1;
        mailing2.setCity("MailingTown2");
        mailing2.setState("MS2");

        person2.setAddresses(Map.of("billing", billingAddress2, "mailing", mailing2));

        PhoneNumber pn2_1 = new PhoneNumber();
        pn2_1.setType("work");
        pn2_1.setNumber("333-333-3333");

        PhoneNumber pn2_2 = new PhoneNumber();
        pn2_2.setType("cell");
        pn2_2.setNumber("444-444-4444");

        List<PhoneNumber> phoneNumbers2 = List.of(pn2_1, pn2_2);
        person2.setPhoneNumbers(phoneNumbers2);

        personDynamoDbTable.putItem(person2);
    }
```

### 数据库中项目的 JSON 表示形式
<a name="nested-attributes-expression-json-items"></a>

```
{
 "id": 1,
 "addresses": {
  "billing": {
   "city": "BillingTown1",
   "state": "BS1",
   "street": null,
   "zipCode": null
  },
  "mailing": {
   "city": "MailingTown1",
   "state": "MS1",
   "street": null,
   "zipCode": null
  }
 },
 "firstName": "FirstName1",
 "lastName": "LastName1",
 "phoneNumbers": [
  {
   "number": "111-111-1111",
   "type": "work"
  },
  {
   "number": "222-222-2222",
   "type": "home"
  }
 ]
}

{
 "id": 2,
 "addresses": {
  "billing": {
   "city": "BillingTown2",
   "state": "BS2",
   "street": null,
   "zipCode": null
  },
  "mailing": {
   "city": "MailingTown2",
   "state": "MS2",
   "street": null,
   "zipCode": null
  }
 },
 "firstName": "FirstName2",
 "lastName": "LastName2",
 "phoneNumbers": [
  {
   "number": "333-333-3333",
   "type": "work"
  },
  {
   "number": "444-444-4444",
   "type": "cell"
  }
 ]
}
```

## 更新包含复杂类型的项目
<a name="ddb-en-client-adv-features-nested-updates"></a>

要更新包含复杂类型的项目，有两种基本方法：
+ 方法 1：先检索项目（使用 `getItem`），更新对象，然后调用 `DynamoDbTable#updateItem`。
+ 方法 2：不检索项目，而是构造一个新实例，设置要更新的属性，然后通过设置适当的 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/IgnoreNullsMode.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/IgnoreNullsMode.html) 值将实例提交到 `DynamoDbTable#updateItem`。这种方法不需要在更新之前获取项目。

此部分中显示的示例使用前面显示的 `Person`、`Address` 和 `PhoneNumber` 类。

### 更新方法 1：检索，然后更新
<a name="ddb-en-client-adv-features-nested-updates-retreive"></a>

通过使用这种方法，您可以确保更新时不会丢失任何数据。DynamoDB 增强型客户端 API 使用保存在 DynamoDB 中的项目中的属性（包括复杂类型的值）重新创建 bean。然后，您需要使用 getter 和 setter 来更新 bean。这种方法的缺点是先检索项目会产生费用。

下面的示例说明：如果在更新项目之前先检索项目，则不会丢失任何数据。

```
    public void retrieveThenUpdateExample()  {
        // Assume that we ran this code yesterday.
        Person person = new Person();
        person.setId(1);
        person.setFirstName("FirstName");
        person.setLastName("LastName");

        Address mainAddress = new Address();
        mainAddress.setStreet("123 MyStreet");
        mainAddress.setCity("MyCity");
        mainAddress.setState("MyState");
        mainAddress.setZipCode("MyZipCode");
        person.setMainAddress(mainAddress);

        PhoneNumber homePhone = new PhoneNumber();
        homePhone.setNumber("1111111");
        homePhone.setType("HOME");
        person.setPhoneNumbers(List.of(homePhone));

        personDynamoDbTable.putItem(person);

        // Assume that we are running this code now.
        // First, retrieve the item
        Person retrievedPerson = personDynamoDbTable.getItem(Key.builder().partitionValue(1).build());

        // Make any updates.
        retrievedPerson.getMainAddress().setCity("YourCity");

        // Save the updated bean. 'updateItem' returns the bean as it appears after the update.
        Person updatedPerson = personDynamoDbTable.updateItem(retrievedPerson);

        // Verify for this example.
        Address updatedMainAddress = updatedPerson.getMainAddress();
        assert updatedMainAddress.getCity().equals("YourCity");
        assert updatedMainAddress.getState().equals("MyState"); // Unchanged.
        // The list of phone numbers remains; it was not set to null;
        assert updatedPerson.getPhoneNumbers().size() == 1;
    }
```

### 更新方法 2：使用 `IgnoreNullsMode` 枚举而不先检索项目
<a name="ddb-en-client-adv-features-nested-updates-nullmode"></a>

要更新 DynamoDB 中的项目，您可以提供一个仅包含待更新属性的新对象，而将其他值保留为 null。使用这种方法，您需要了解 SDK 如何处理对象中的 null 值以及如何控制行为。

要指定希望 SDK 忽略哪些 null 值属性，请在构建 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/UpdateItemEnhancedRequest.Builder.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/UpdateItemEnhancedRequest.Builder.html) 时提供 `IgnoreNullsMode` 枚举。作为枚举值使用的一个示例，以下代码段使用了 `IgnoreNullsMode.SCALAR_ONLY` 模式。

```
// Create a new Person object to update the existing item in DynamoDB.
Person personForUpdate = new Person();
personForUpdate.setId(1);
personForUpdate.setFirstName("updatedFirstName");  // 'firstName' is a top scalar property.

Address addressForUpdate = new Address();
addressForUpdate.setCity("updatedCity");
personForUpdate.setMainAddress(addressForUpdate);

personDynamoDbTable.updateItem(r -> r
                .item(personForUpdate)
                .ignoreNullsMode(IgnoreNullsMode.SCALAR_ONLY));

/* With IgnoreNullsMode.SCALAR_ONLY provided, The SDK ignores all null properties. The SDK adds or replaces
the 'firstName' property with the provided value, "updatedFirstName". The SDK updates the 'city' value of
'mainAddress', as long as the 'mainAddress' attribute already exists in DynamoDB.

In the background, the SDK generates an update expression that it sends in the request to DynamoDB.
The following JSON object is a simplified version of what it sends. Notice that the SDK includes the paths
to 'mainAddress.city' and 'firstName' in the SET clause of the update expression. No null values in
'personForUpdate' are included.

{
  "TableName": "PersonTable",
  "Key": {
    "id": {
      "N": "1"
    }
  },
  "ReturnValues": "ALL_NEW",
  "UpdateExpression": "SET #mainAddress.#city = :mainAddress_city, #firstName = :firstName",
  "ExpressionAttributeNames": {
    "#city": "city",
    "#firstName": "firstName",
    "#mainAddress": "mainAddress"
  },
  "ExpressionAttributeValues": {
    ":firstName": {
      "S": "updatedFirstName"
    },
    ":mainAddress_city": {
      "S": "updatedCity"
    }
  }
}

Had we chosen 'IgnoreNullsMode.DEFAULT' instead of 'IgnoreNullsMode.SCALAR_ONLY', the SDK would have included
null values in the "ExpressionAttributeValues" section of the request as shown in the following snippet.

  "ExpressionAttributeValues": {
    ":mainAddress": {
      "M": {
        "zipCode": {
          "NULL": true
        },
        "city": {
          "S": "updatedCity"
        },
        "street": {
          "NULL": true
        },
        "state": {
          "NULL": true
        }
      }
    },
    ":firstName": {
      "S": "updatedFirstName"
    }
  }
*/
```

《Amazon DynamoDB 开发人员指南》包含了有关[更新表达式](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html)的更多信息。

#### `IgnoreNullsMode` 选项的描述
<a name="ignore-nulls-mode-descriptions"></a>
+ `IgnoreNullsMode.SCALAR_ONLY` - 使用此设置在任何级别更新标量属性。SDK 会构造一个更新语句，该语句仅向 DynamoDB 发送非 null 的标量属性。SDK 会忽略 bean 或 map 的 null 值标量属性，保留在 DynamoDB 中保存的值。

  更新 map 或 bean 的标量属性时，map 必须已存在于 DynamoDB 中。如果 DynamoDB 中的对象尚不存在您向对象添加的 map 或 bean，则您会收到 `DynamoDbException`，其中包含消息：*在更新表达式中提供的文档路径对于更新无效*。您必须使用 `MAPS_ONLY` 模式，先将 bean 或 map 添加到 DynamoDB 中，然后才能更新其属性。
+ `IgnoreNullsMode.MAPS_ONLY` - 使用此设置添加或替换 bean 或 map 的属性。SDK 会替换或添加对象中提供的任何 map 或 bean。系统会忽略对象中为 null 的任何 bean 或 map，并保留 DynamoDB 中存在的 map。
+ `IgnoreNullsMode.DEFAULT` - 使用此设置，SDK 永远不会忽略 null 值。任何级别的标量属性如果为 null，则更新为 null。在 DynamoDB 中，SDK 会将对象中的任何 null 值 bean、map、list 或 set 属性更新为 null。当您使用此模式（或者由于这是默认模式而不提供模式）时，应先检索项目，以便 DynamoDB 中的值不会设置为更新对象中提供的 null，除非您确实有意将这些值设置为 null。

在所有模式下，如果您将具有非 null 值 list 或 set 属性的对象提供给 `updateItem`，则 list 或 set 会保存到 DynamoDB 中。

#### 为什么使用这些模式？
<a name="ddb-en-client-adv-features-nested-updates-nullmodes-why"></a>

当你为对象提供一个 Bean 或`updateItem`方法映射时，SDK 无法判断是否应该使用 Bean 中的属性值（或地图中的条目值）来更新项目，或者整个属性值是否 bean/map 应该替换保存到 DynamoDB 中的内容。

基于我们之前“先检索项再更新”的示例，现在尝试在不检索的情况下直接更新 `mainAddress` 的 `city` 属性。

```
/* The retrieval example saved the Person object with a 'mainAddress' property whose 'city' property value is "MyCity".
/* Note that we create a new Person with only the necessary information to update the city value
of the mainAddress. */
Person personForUpdate = new Person();
personForUpdate.setId(1);
// The update we want to make changes the city.
Address mainAddressForUpdate = new Address();
mainAddressForUpdate.setCity("YourCity");
personForUpdate.setMainAddress(mainAddressForUpdate);

// Lets' try the following:
Person updatedPerson = personDynamoDbTable.updateItem(personForUpdate);
/*
 Since we haven't retrieved the item, we don't know if the 'mainAddress' property
 already exists, so what update expression should the SDK generate?

A) Should it replace or add the 'mainAddress' with the provided object (setting all attributes to null other than city)
   as shown in the following simplified JSON?

      {
        "TableName": "PersonTable",
        "Key": {
          "id": {
            "N": "1"
          }
        },
        "ReturnValues": "ALL_NEW",
        "UpdateExpression": "SET #mainAddress = :mainAddress",
        "ExpressionAttributeNames": {
          "#mainAddress": "mainAddress"
        },
        "ExpressionAttributeValues": {
          ":mainAddress": {
            "M": {
              "zipCode": {
                "NULL": true
              },
              "city": {
                "S": "YourCity"
              },
              "street": {
                "NULL": true
              },
              "state": {
                "NULL": true
              }
            }
          }
        }
      }
 
B) Or should it update only the 'city' attribute of an existing 'mainAddress' as shown in the following simplified JSON?

      {
        "TableName": "PersonTable",
        "Key": {
          "id": {
            "N": "1"
          }
        },
        "ReturnValues": "ALL_NEW",
        "UpdateExpression": "SET #mainAddress.#city = :mainAddress_city",
        "ExpressionAttributeNames": {
          "#city": "city",
          "#mainAddress": "mainAddress"
        },
        "ExpressionAttributeValues": {
          ":mainAddress_city": {
            "S": "YourCity"
          }
        }
      }

However, assume that we don't know if the 'mainAddress' already exists. If it doesn't exist, the SDK would try to update 
an attribute of a non-existent map, which results in an exception.

In this particular case, we would likely select option B (SCALAR_ONLY) to retain the other values of the 'mainAddress'.
*/
```

以下两个示例展示了 `MAPS_ONLY` 和 `SCALAR_ONLY` 枚举值的用法。`MAPS_ONLY` 添加 map，而 `SCALAR_ONLY` 更新 map。

##### `IgnoreNullsMode.MAPS_ONLY` 示例
<a name="scalar-only-example"></a>

```
    public void mapsOnlyModeExample() {
        // Assume that we ran this code yesterday.
        Person person = new Person();
        person.setId(1);
        person.setFirstName("FirstName");

        personDynamoDbTable.putItem(person);

        // Assume that we are running this code now.

        /* Note that we create a new Person with only the necessary information to update the city value
        of the mainAddress. */
        Person personForUpdate = new Person();
        personForUpdate.setId(1);
        // The update we want to make changes the city.
        Address mainAddressForUpdate = new Address();
        mainAddressForUpdate.setCity("YourCity");
        personForUpdate.setMainAddress(mainAddressForUpdate);


        Person updatedPerson = personDynamoDbTable.updateItem(r -> r
                .item(personForUpdate)
                .ignoreNullsMode(IgnoreNullsMode.MAPS_ONLY)); // Since the mainAddress property does not exist, use MAPS_ONLY mode.
        assert updatedPerson.getMainAddress().getCity().equals("YourCity");
        assert updatedPerson.getMainAddress().getState() == null;
    }
```

##### `IgnoreNullsMode.SCALAR_ONLY example`
<a name="maps-only-example"></a>

```
    public void scalarOnlyExample() {
        // Assume that we ran this code yesterday.
        Person person = new Person();
        person.setId(1);
        Address mainAddress = new Address();
        mainAddress.setCity("MyCity");
        mainAddress.setState("MyState");
        person.setMainAddress(mainAddress);

        personDynamoDbTable.putItem(person);

        // Assume that we are running this code now.

        /* Note that we create a new Person with only the necessary information to update the city value
        of the mainAddress. */
        Person personForUpdate = new Person();
        personForUpdate.setId(1);
        // The update we want to make changes the city.
        Address mainAddressForUpdate = new Address();
        mainAddressForUpdate.setCity("YourCity");
        personForUpdate.setMainAddress(mainAddressForUpdate);

        Person updatedPerson = personDynamoDbTable.updateItem(r -> r
                .item(personForUpdate)
                .ignoreNullsMode(IgnoreNullsMode.SCALAR_ONLY)); // SCALAR_ONLY mode ignores null properties in the in mainAddress.
        assert updatedPerson.getMainAddress().getCity().equals("YourCity");
        assert updatedPerson.getMainAddress().getState().equals("MyState"); // The state property remains the same.
    }
```

请参阅下表，了解每种模式忽略哪些 null 值。除非您在处理 bean 或 map，否则通常可以使用 `SCALAR_ONLY` 或 `MAPS_ONLY` 中的任意一种。


**对于每种模式，SDK 会忽略提交给 `updateItem` 的对象中的哪些 null 值属性？**  

| 属性的类型 | 在 SCALAR\$1ONLY 模式下 | 在 MAPS\$1ONLY 模式下 | 在 DEFAULT 模式下 | 
| --- | --- | --- | --- | 
| 顶层标量 | 支持 | 是 | 否 | 
| bean 或 map | 支持 | 是 | 否 | 
| bean 或 map 条目的标量值 | 是1 | 否2 | 否 | 
| list 或 set | 支持 | 是 | 否 | 

1假设 map 已存在于 DynamoDB 中。您在更新对象中提供的 bean 或 map 的任何标量值（null 或非 null）都要求 DynamoDB 中存在该值的路径。SDK 在提交请求之前会使用 `. (dot)` 解除引用操作符来构造指向该属性的路径。

2由于您使用 `MAPS_ONLY` 模式来完全替换或添加 bean 或 map，因此 bean 或 map 中的所有 null 值都将在保存到 DynamoDB 的 map 中保留。

# 使用 `@DynamoDbPreserveEmptyObject` 保留空对象
<a name="ddb-en-client-adv-features-empty"></a>

如果您将包含空对象的 Bean 保存到 Amazon DynamoDB 中，并且希望 SDK 在检索时重新创建空对象，请使用 `@DynamoDbPreserveEmptyObject` 注释内部 Bean 的 getter。

为了说明该注释的工作原理，代码示例使用了以下两个 Bean。

## 示例 Bean
<a name="ddb-en-client-adv-features-empty-ex1"></a>

以下数据类包含两个 `InnerBean` 字段。getter 方法 `getInnerBeanWithoutAnno()` 不使用 `@DynamoDbPreserveEmptyObject` 注释。`getInnerBeanWithAnno()` 方法使用注释。

```
@DynamoDbBean
public class MyBean {

    private String id;
    private String name;
    private InnerBean innerBeanWithoutAnno;
    private InnerBean innerBeanWithAnno;

    @DynamoDbPartitionKey
    public String getId() { return id; }
    public void setId(String id) { this.id = id; }

    public String getName() { return name; }
    public void setName(String name) { this.name = name; }

    public InnerBean getInnerBeanWithoutAnno() { return innerBeanWithoutAnno; }
    public void setInnerBeanWithoutAnno(InnerBean innerBeanWithoutAnno) { this.innerBeanWithoutAnno = innerBeanWithoutAnno; }

    @DynamoDbPreserveEmptyObject
    public InnerBean getInnerBeanWithAnno() { return innerBeanWithAnno; }
    public void setInnerBeanWithAnno(InnerBean innerBeanWithAnno) { this.innerBeanWithAnno = innerBeanWithAnno; }

    @Override
    public String toString() {
        return new StringJoiner(", ", MyBean.class.getSimpleName() + "[", "]")
                .add("innerBeanWithoutAnno=" + innerBeanWithoutAnno)
                .add("innerBeanWithAnno=" + innerBeanWithAnno)
                .add("id='" + id + "'")
                .add("name='" + name + "'")
                .toString();
    }
}
```

以下 `InnerBean` 类的实例是 `MyBean` 的字段，并且在示例代码中被初始化为空对象。

```
@DynamoDbBean
public class InnerBean {

    private String innerBeanField;

    public String getInnerBeanField() {
        return innerBeanField;
    }

    public void setInnerBeanField(String innerBeanField) {
        this.innerBeanField = innerBeanField;
    }

    @Override
    public String toString() {
        return "InnerBean{" +
                "innerBeanField='" + innerBeanField + '\'' +
                '}';
    }
}
```

以下代码示例将带有初始化内部 Bean 的 `MyBean` 对象保存到 DynamoDB，然后检索该项目。记录的输出显示 `innerBeanWithoutAnno` 未初始化，但已创建 `innerBeanWithAnno`。

```
    public MyBean preserveEmptyObjectAnnoUsingGetItemExample(DynamoDbTable<MyBean> myBeanTable) {
        // Save an item to DynamoDB.
        MyBean bean = new MyBean();
        bean.setId("1");
        bean.setInnerBeanWithoutAnno(new InnerBean());   // Instantiate the inner bean.
        bean.setInnerBeanWithAnno(new InnerBean());      // Instantiate the inner bean.
        myBeanTable.putItem(bean);

        GetItemEnhancedRequest request = GetItemEnhancedRequest.builder()
                .key(Key.builder().partitionValue("1").build())
                .build();
        MyBean myBean = myBeanTable.getItem(request);

        logger.info(myBean.toString());
        // Output 'MyBean[innerBeanWithoutAnno=null, innerBeanWithAnno=InnerBean{innerBeanField='null'}, id='1', name='null']'.

        return myBean;
    }
```

## 替代静态架构
<a name="ddb-en-client-adv-features-empty-ex1-static"></a>

您可以使用以下 `StaticTableSchema` 版本的表架构来代替 Bean 上的注释。

```
    public static TableSchema<MyBean> buildStaticSchemas() {

        StaticTableSchema<InnerBean> innerBeanStaticTableSchema =
                StaticTableSchema.builder(InnerBean.class)
                        .newItemSupplier(InnerBean::new)
                        .addAttribute(String.class, a -> a.name("innerBeanField")
                                .getter(InnerBean::getInnerBeanField)
                                .setter(InnerBean::setInnerBeanField))
                        .build();

        return StaticTableSchema.builder(MyBean.class)
                .newItemSupplier(MyBean::new)
                .addAttribute(String.class, a -> a.name("id")
                        .getter(MyBean::getId)
                        .setter(MyBean::setId)
                        .addTag(primaryPartitionKey()))
                .addAttribute(String.class, a -> a.name("name")
                        .getter(MyBean::getName)
                        .setter(MyBean::setName))
                .addAttribute(EnhancedType.documentOf(InnerBean.class,
                                innerBeanStaticTableSchema),
                        a -> a.name("innerBean1")
                                .getter(MyBean::getInnerBeanWithoutAnno)
                                .setter(MyBean::setInnerBeanWithoutAnno))
                .addAttribute(EnhancedType.documentOf(InnerBean.class,
                                innerBeanStaticTableSchema,
                                b -> b.preserveEmptyObject(true)),
                        a -> a.name("innerBean2")
                                .getter(MyBean::getInnerBeanWithAnno)
                                .setter(MyBean::setInnerBeanWithAnno))
                .build();
    }
```

# 避免保存嵌套对象的空属性
<a name="ddb-en-client-adv-features-ignore-null"></a>

在将数据类对象保存到 DynamoDB 时，您可以通过应用 `@DynamoDbIgnoreNulls` 注释来跳过嵌套对象的空属性。相比之下，具有空值的顶级属性永远不会保存到数据库中。

为了说明该注释的工作原理，代码示例使用了以下两个 Bean。

## 示例 Bean
<a name="ddb-en-client-adv-features-ignore-null-ex1"></a>

以下数据类包含两个 `InnerBean` 字段。getter 方法 `getInnerBeanWithoutAnno()` 不使用注释。`getInnerBeanWithIgnoreNullsAnno()` 方法使用注释 `@DynamoDbIgnoreNulls`。

```
@DynamoDbBean
public class MyBean {

    private String id;
    private String name;
    private InnerBean innerBeanWithoutAnno;
    private InnerBean innerBeanWithIgnoreNullsAnno;

    @DynamoDbPartitionKey
    public String getId() { return id; }
    public void setId(String id) { this.id = id; }

    public String getName() { return name; }
    public void setName(String name) { this.name = name; }

    public InnerBean getInnerBeanWithoutAnno() { return innerBeanWithoutAnno; }
    public void setInnerBeanWithoutAnno(InnerBean innerBeanWithoutAnno) { this.innerBeanWithoutAnno = innerBeanWithoutAnno; }

    @DynamoDbIgnoreNulls
    public InnerBean getInnerBeanWithIgnoreNullsAnno() { return innerBeanWithIgnoreNullsAnno; }
    public void setInnerBeanWithIgnoreNullsAnno(InnerBean innerBeanWithAnno) { this.innerBeanWithIgnoreNullsAnno = innerBeanWithAnno; }

    @Override
    public String toString() {
        return new StringJoiner(", ", MyBean.class.getSimpleName() + "[", "]")
                .add("innerBeanWithoutAnno=" + innerBeanWithoutAnno)
                .add("innerBeanWithIgnoreNullsAnno=" + innerBeanWithIgnoreNullsAnno)
                .add("id='" + id + "'")
                .add("name='" + name + "'")
                .toString();
    }
}
```

以下 `InnerBean` 类的实例是 `MyBean` 的字段，用于以下示例代码。

```
@DynamoDbBean
public class InnerBean {

    private String innerBeanFieldString;
    private Integer innerBeanFieldInteger;

    public String getInnerBeanFieldString() { return innerBeanFieldString; }
    public void setInnerBeanFieldString(String innerBeanFieldString) { this.innerBeanFieldString = innerBeanFieldString; }

    public Integer getInnerBeanFieldInteger() { return innerBeanFieldInteger; }
    public void setInnerBeanFieldInteger(Integer innerBeanFieldInteger) { this.innerBeanFieldInteger = innerBeanFieldInteger; }

    @Override
    public String toString() {
        return new StringJoiner(", ", InnerBean.class.getSimpleName() + "[", "]")
                .add("innerBeanFieldString='" + innerBeanFieldString + "'")
                .add("innerBeanFieldInteger=" + innerBeanFieldInteger)
                .toString();
    }
}
```

以下代码示例创建一个 `InnerBean` 对象，并仅为其两个属性中的一个设置了值。

```
    public void ignoreNullsAnnoUsingPutItemExample(DynamoDbTable<MyBean> myBeanTable) {
        // Create an InnerBean object and give only one attribute a value.
        InnerBean innerBeanOneAttributeSet = new InnerBean();
        innerBeanOneAttributeSet.setInnerBeanFieldInteger(200);

        // Create a MyBean instance and use the same InnerBean instance both for attributes.
        MyBean bean = new MyBean();
        bean.setId("1");
        bean.setInnerBeanWithoutAnno(innerBeanOneAttributeSet);
        bean.setInnerBeanWithIgnoreNullsAnno(innerBeanOneAttributeSet);

        Map<String, AttributeValue> itemMap = myBeanTable.tableSchema().itemToMap(bean, true);
        logger.info(itemMap.toString());
        // Log the map that is sent to the database.
        // {innerBeanWithIgnoreNullsAnno=AttributeValue(M={innerBeanFieldInteger=AttributeValue(N=200)}), id=AttributeValue(S=1), innerBeanWithoutAnno=AttributeValue(M={innerBeanFieldInteger=AttributeValue(N=200), innerBeanFieldString=AttributeValue(NUL=true)})}
        
        // Save the MyBean object to the table.
        myBeanTable.putItem(bean);
    }
```

为了可视化发送到 DynamoDB 的低级别数据，该代码会在保存 `MyBean` 对象之前记录属性映射。

记录的输出显示，`innerBeanWithIgnoreNullsAnno` 输出了一个属性，

```
innerBeanWithIgnoreNullsAnno=AttributeValue(M={innerBeanFieldInteger=AttributeValue(N=200)})
```

`innerBeanWithoutAnno` 实例输出了两个属性。一个属性的值为 200，另一个属性的值为空。

```
innerBeanWithoutAnno=AttributeValue(M={innerBeanFieldInteger=AttributeValue(N=200), innerBeanFieldString=AttributeValue(NUL=true)})
```

## 属性映射的 JSON 表示
<a name="ddb-en-client-adv-features-ignore-null-ex2"></a>

以下 JSON 表示可以更轻松地查看保存到 DynamoDB 的数据。

```
{
  "id": {
    "S": "1"
  },
  "innerBeanWithIgnoreNullsAnno": {
    "M": {
      "innerBeanFieldInteger": {
        "N": "200"
      }
    }
  },
  "innerBeanWithoutAnno": {
    "M": {
      "innerBeanFieldInteger": {
        "N": "200"
      },
      "innerBeanFieldString": {
        "NULL": true
      }
    }
  }
}
```

## 替代静态架构
<a name="ddb-en-client-adv-features-empty-ex1-static"></a>

您可以使用以下 `StaticTableSchema` 版本的表架构来代替数据类标注。

```
public static TableSchema<MyBean> buildStaticSchemas() {

    StaticTableSchema<InnerBean> innerBeanStaticTableSchema =
        StaticTableSchema.builder(InnerBean.class)
            .newItemSupplier(InnerBean::new)
            .addAttribute(String.class, a -> a.name("innerBeanFieldString")
                .getter(InnerBean::getInnerBeanFieldString)
                .setter(InnerBean::setInnerBeanFieldString))
            .addAttribute(Integer.class, a -> a.name("innerBeanFieldInteger")
                .getter(InnerBean::getInnerBeanFieldInteger)
                .setter(InnerBean::setInnerBeanFieldInteger))
            .build();

    return StaticTableSchema.builder(MyBean.class)
        .newItemSupplier(MyBean::new)
        .addAttribute(String.class, a -> a.name("id")
            .getter(MyBean::getId)
            .setter(MyBean::setId)
            .addTag(primaryPartitionKey()))
        .addAttribute(String.class, a -> a.name("name")
            .getter(MyBean::getName)
            .setter(MyBean::setName))
        .addAttribute(EnhancedType.documentOf(InnerBean.class,
                innerBeanStaticTableSchema),
            a -> a.name("innerBeanWithoutAnno")
                .getter(MyBean::getInnerBeanWithoutAnno)
                .setter(MyBean::setInnerBeanWithoutAnno))
        .addAttribute(EnhancedType.documentOf(InnerBean.class,
                innerBeanStaticTableSchema,
                b -> b.ignoreNulls(true)),
            a -> a.name("innerBeanWithIgnoreNullsAnno")
                .getter(MyBean::getInnerBeanWithIgnoreNullsAnno)
                .setter(MyBean::setInnerBeanWithIgnoreNullsAnno))
        .build();
}
```

# 使用适用于 DynamoDB 的增强型文档 API 处理 JSON 文档
<a name="ddb-en-client-doc-api"></a>

的[增强型文档 API](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/document/package-summary.html) AWS SDK for Java 2.x 旨在处理没有固定架构的面向文档的数据。该 API 也允许您使用自定义类来映射单个属性。

 增强型文档 API 是 适用于 Java 的 AWS SDK 1.x 版[文档 API](https://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/services/dynamodbv2/document/DynamoDB.html) 的继任者。

**Contents**
+ [开始使用增强型文档 API](ddb-en-client-doc-api-steps.md)
  + [创建 `DocumentTableSchema` 和 `DynamoDbTable`。](ddb-en-client-doc-api-steps.md#ddb-en-client-doc-api-steps-createschema)
+ [生成增强型文档](ddb-en-client-doc-api-steps-create-ed.md)
  + [使用 JSON 字符串生成](ddb-en-client-doc-api-steps-create-ed.md#ddb-en-client-doc-api-steps-create-ed-fromJson)
  + [基于单个元素构建](ddb-en-client-doc-api-steps-create-ed.md#ddb-en-client-doc-api-steps-create-ed-fromparts)
+ [执行 CRUD 操作](ddb-en-client-doc-api-steps-use.md)
+ [将增强型文档属性作为自定义对象进行访问](ddb-en-client-doc-api-convert.md)
+ [在没有 DynamoDB 的情况下使用 `EnhancedDocument`](ddb-en-client-doc-api-standalone.md)

# 开始使用增强型文档 API
<a name="ddb-en-client-doc-api-steps"></a>

增强型文档 API 所需的[依赖项](ddb-en-client-getting-started.md#ddb-en-client-gs-dep)与 DynamoDB 增强型客户端 API 所需的依赖项相同。它还需要一个 [`DynamoDbEnhancedClient` 实例](ddb-en-client-getting-started-dynamodbTable.md#ddb-en-client-getting-started-dynamodbTable-eclient)，如本主题开头所示。

由于增强型文档 API 是在 2.20.3 版本中发布的 AWS SDK for Java 2.x，因此您需要该版本或更高版本。

## 创建 `DocumentTableSchema` 和 `DynamoDbTable`。
<a name="ddb-en-client-doc-api-steps-createschema"></a>

要使用增强文档 API 对 DynamoDB 表调用命令，请将该表与[DynamoDbTable客户端 EnhancedDocument](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbTable.html) < > 资源对象相关联。

增强型客户端的 `table()` 方法会创建一个 `DynamoDbTable<EnhancedDocument>` 实例，并且需要用于 DynamoDB 表名称和 `DocumentTableSchema` 的参数。

的生成器[DocumentTableSchema](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/document/DocumentTableSchema.html)需要主索引键和一个或多个属性转换器提供程序。`AttributeConverterProvider.defaultProvider()` 方法为[默认类型](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/internal/converter/attribute/package-summary.html)提供转换器。即使您提供了自定义属性转换器提供程序，也应指定它。您可以向生成器添加可选的二级索引键。

以下代码段显示的代码将生成一个 DynamoDB `person` 表的客户端表示形式，该表将存储无架构 `EnhancedDocument` 对象。

```
DynamoDbTable<EnhancedDocument> documentDynamoDbTable = 
                enhancedClient.table("person",
                        TableSchema.documentSchemaBuilder()
                            // Specify the primary key attributes.
                            .addIndexPartitionKey(TableMetadata.primaryIndexName(),"id", AttributeValueType.S)
                            .addIndexSortKey(TableMetadata.primaryIndexName(), "lastName", AttributeValueType.S)
                            // Specify attribute converter providers. Minimally add the default one.
                            .attributeConverterProviders(AttributeConverterProvider.defaultProvider())
                            .build());
                                                         
// Call documentTable.createTable() if "person" does not exist in DynamoDB.
// createTable() should be called only one time.
```

以下显示了本部分中使用的 `person` 对象的 JSON 表示形式。

### JSON `person` 对象
<a name="ddb-en-client-doc-api-steps-createschema-obj"></a>

```
{
  "id": 1,
  "firstName": "Richard",
  "lastName": "Roe",
  "age": 25,
  "addresses":
    {
      "home": {
        "zipCode": "00000",
        "city": "Any Town",
        "state": "FL",
        "street": "123 Any Street"
      },
      "work": {
        "zipCode": "00001",
        "city": "Anywhere",
        "state": "FL",
        "street": "100 Main Street"
      }
    },
  "hobbies": [
    "Hobby 1",
    "Hobby 2"
  ],
  "phoneNumbers": [
    {
      "type": "Home",
      "number": "555-0100"
    },
    {
      "type": "Work",
      "number": "555-0119"
    }
  ]
}
```

# 生成增强型文档
<a name="ddb-en-client-doc-api-steps-create-ed"></a>

`[EnhancedDocument](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/document/EnhancedDocument.html)` 表示具有复杂结构和嵌套属性的文档类型对象。`EnhancedDocument` 需要与为 `DocumentTableSchema` 指定的主键属性相匹配的顶级属性。其余内容是任意的，可以由顶级属性以及深度嵌套的属性组成。

您可以使用提供多种元素添加方法的生成器来创建 `EnhancedDocument` 实例。

## 使用 JSON 字符串生成
<a name="ddb-en-client-doc-api-steps-create-ed-fromJson"></a>

使用 JSON 字符串，您可以在一个方法调用中生成 `EnhancedDocument`。以下代码段从 `jsonPerson()` 帮助程序方法返回的 JSON 字符串创建一个 `EnhancedDocument`。`jsonPerson()` 方法返回前面显示的 [person 对象](ddb-en-client-doc-api-steps.md#ddb-en-client-doc-api-steps-createschema-obj)的 JSON 字符串版本。

```
EnhancedDocument document = 
        EnhancedDocument.builder()
                        .json( jsonPerson() )
                        .build());
```

## 基于单个元素构建
<a name="ddb-en-client-doc-api-steps-create-ed-fromparts"></a>

或者，您可以使用生成器的类型安全方法从各个组件生成 `EnhancedDocument` 实例。

以下示例生成一个 `person` 增强型文档，该文档类似于上一个示例中从 JSON 字符串生成的增强型文档。

```
        /* Define the shape of an address map whose JSON representation looks like the following.
           Use 'addressMapEnhancedType' in the following EnhancedDocument.builder() to simplify the code.
           "home": {
             "zipCode": "00000",
             "city": "Any Town",
             "state": "FL",
             "street": "123 Any Street"
           }*/
        EnhancedType<Map<String, String>> addressMapEnhancedType =
                EnhancedType.mapOf(EnhancedType.of(String.class), EnhancedType.of(String.class));


        //  Use the builder's typesafe methods to add elements to the enhanced document.
        EnhancedDocument personDocument = EnhancedDocument.builder()
                .putNumber("id", 50)
                .putString("firstName", "Shirley")
                .putString("lastName", "Rodriguez")
                .putNumber("age", 53)
                .putNull("nullAttribute")
                .putJson("phoneNumbers", phoneNumbersJSONString())
                /* Add the map of addresses whose JSON representation looks like the following.
                        {
                          "home": {
                            "zipCode": "00000",
                            "city": "Any Town",
                            "state": "FL",
                            "street": "123 Any Street"
                          }
                        } */
                .putMap("addresses", getAddresses(), EnhancedType.of(String.class), addressMapEnhancedType)
                .putList("hobbies", List.of("Theater", "Golf"), EnhancedType.of(String.class))
                .build();
```

### 帮助程序方法
<a name="ddb-en-client-doc-api-steps-use-fromparts-helpers"></a>

```
    private static String phoneNumbersJSONString() {
        return "  [" +
                "    {" +
                "      \"type\": \"Home\"," +
                "      \"number\": \"555-0140\"" +
                "    }," +
                "    {" +
                "      \"type\": \"Work\"," +
                "      \"number\": \"555-0155\"" +
                "    }" +
                "  ]";
    }

    private static Map<String, Map<String, String>> getAddresses() {
        return Map.of(
                "home", Map.of(
                        "zipCode", "00002",
                        "city", "Any Town",
                        "state", "ME",
                        "street", "123 Any Street"));

    }
```

# 执行 CRUD 操作
<a name="ddb-en-client-doc-api-steps-use"></a>

定义 `EnhancedDocument` 实例后，您可以将其保存到 DynamoDB 表。以下代码段使用基于单个元素创建的 [personDocument](ddb-en-client-doc-api-steps-create-ed.md#ddb-en-client-doc-api-steps-create-ed-fromparts)。

```
documentDynamoDbTable.putItem(personDocument);
```

读取 DynamoDB 中的增强型文档实例后，您可以使用 getter 提取各个属性值，如以下代码段所示，这些代码段用于访问从 `personDocument` 中保存的数据。或者，您可以将完整内容提取为 JSON 字符串，如示例代码的最后一部分所示。

```
        // Read the item.
        EnhancedDocument personDocFromDb = documentDynamoDbTable.getItem(Key.builder().partitionValue(50).build());

        // Access top-level attributes.
        logger.info("Name: {} {}", personDocFromDb.getString("firstName"), personDocFromDb.getString("lastName"));
        // Name: Shirley Rodriguez

        // Typesafe access of a deeply nested attribute. The addressMapEnhancedType shown previously defines the shape of an addresses map.
        Map<String, Map<String, String>> addresses = personDocFromDb.getMap("addresses", EnhancedType.of(String.class), addressMapEnhancedType);
        addresses.keySet().forEach(k -> logger.info(addresses.get(k).toString()));
        // {zipCode=00002, city=Any Town, street=123 Any Street, state=ME}

        // Alternatively, work with AttributeValue types checking along the way for deeply nested attributes.
        Map<String, AttributeValue> addressesMap = personDocFromDb.getMapOfUnknownType("addresses");
        addressesMap.keySet().forEach((String k) -> {
            logger.info("Looking at data for [{}] address", k);
            // Looking at data for [home] address
            AttributeValue value = addressesMap.get(k);
            AttributeValue cityValue = value.m().get("city");
            if (cityValue != null) {
                logger.info(cityValue.s());
                // Any Town
            }
        });

        List<AttributeValue> phoneNumbers = personDocFromDb.getListOfUnknownType("phoneNumbers");
        phoneNumbers.forEach((AttributeValue av) -> {
            if (av.hasM()) {
                AttributeValue type = av.m().get("type");
                if (type.s() != null) {
                    logger.info("Type of phone: {}", type.s());
                    // Type of phone: Home
                    // Type of phone: Work
                }
            }
        });

        String jsonPerson = personDocFromDb.toJson();
        logger.info(jsonPerson);
        // {"firstName":"Shirley","lastName":"Rodriguez","addresses":{"home":{"zipCode":"00002","city":"Any Town","street":"123 Any Street","state":"ME"}},"hobbies":["Theater","Golf"],
        //     "id":50,"nullAttribute":null,"age":53,"phoneNumbers":[{"number":"555-0140","type":"Home"},{"number":"555-0155","type":"Work"}]}
```

`EnhancedDocument`实例可以与映射的数据类的任何方法一起使用 [DynamoDbEnhancedClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedClient.html)，`[DynamoDbTable](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbTable.html)`也可以代替映射的数据类。

# 将增强型文档属性作为自定义对象进行访问
<a name="ddb-en-client-doc-api-convert"></a>

除了提供用于读取和写入具有无架构结构的属性的 API 之外，增强型文档 API 还允许您在自定义类的实例之间转换属性。

增强型文档 API 使用[控制属性转换](ddb-en-client-adv-features-conversion.md)部分中显示的 `AttributeConverterProvider` 和 `AttributeConverter`，作为 DynamoDB 增强型客户端 API 的一部分。

在以下示例中，我们使用 `CustomAttributeConverterProvider` 及其嵌套 `AddressConverter` 类来转换 `Address` 对象。

此示例表明，您可以混合类中的数据以及根据需要构建的结构中的数据。此示例还表明，自定义类可以在嵌套结构的任何级别上使用。此示例中的 `Address` 对象是映射中使用的值。

```
    public static void attributeToAddressClassMappingExample(DynamoDbEnhancedClient enhancedClient, DynamoDbClient standardClient) {
        String tableName = "customer";

        // Define the DynamoDbTable for an enhanced document.
        // The schema builder provides methods for attribute converter providers and keys.
        DynamoDbTable<EnhancedDocument> documentDynamoDbTable = enhancedClient.table(tableName,
                DocumentTableSchema.builder()
                        // Add the CustomAttributeConverterProvider along with the default when you build the table schema.
                        .attributeConverterProviders(
                                List.of(
                                        new CustomAttributeConverterProvider(),
                                        AttributeConverterProvider.defaultProvider()))
                        .addIndexPartitionKey(TableMetadata.primaryIndexName(), "id", AttributeValueType.N)
                        .addIndexSortKey(TableMetadata.primaryIndexName(), "lastName", AttributeValueType.S)
                        .build());
        // Create the DynamoDB table if needed.
        documentDynamoDbTable.createTable();
        waitForTableCreation(tableName, standardClient);


        // The getAddressesForCustomMappingExample() helper method that provides 'addresses' shows the use of a custom Address class
        // rather than using a Map<String, Map<String, String> to hold the address data.
        Map<String, Address> addresses = getAddressesForCustomMappingExample();

        // Build an EnhancedDocument instance to save an item with a mix of structures defined as needed and static classes.
        EnhancedDocument personDocument = EnhancedDocument.builder()
                .putNumber("id", 50)
                .putString("firstName", "Shirley")
                .putString("lastName", "Rodriguez")
                .putNumber("age", 53)
                .putNull("nullAttribute")
                .putJson("phoneNumbers", phoneNumbersJSONString())
                // Note the use of 'EnhancedType.of(Address.class)' instead of the more generic
                // 'EnhancedType.mapOf(EnhancedType.of(String.class), EnhancedType.of(String.class))' that was used in a previous example.
                .putMap("addresses", addresses, EnhancedType.of(String.class), EnhancedType.of(Address.class))
                .putList("hobbies", List.of("Hobby 1", "Hobby 2"), EnhancedType.of(String.class))
                .build();
        // Save the item to DynamoDB.
        documentDynamoDbTable.putItem(personDocument);

        // Retrieve the item just saved.
        EnhancedDocument srPerson = documentDynamoDbTable.getItem(Key.builder().partitionValue(50).sortValue("Rodriguez").build());

        // Access the addresses attribute.
        Map<String, Address> srAddresses = srPerson.get("addresses",
                EnhancedType.mapOf(EnhancedType.of(String.class), EnhancedType.of(Address.class)));

        srAddresses.keySet().forEach(k -> logger.info(addresses.get(k).toString()));

        documentDynamoDbTable.deleteTable();

// The content logged to the console shows that the saved maps were converted to Address instances.
Address{street='123 Main Street', city='Any Town', state='NC', zipCode='00000'}
Address{street='100 Any Street', city='Any Town', state='NC', zipCode='00000'}
```

## `CustomAttributeConverterProvider` 代码
<a name="ddb-en-client-doc-api-convert-provider"></a>

```
public class CustomAttributeConverterProvider implements AttributeConverterProvider {

    private final Map<EnhancedType<?>, AttributeConverter<?>> converterCache = ImmutableMap.of(
            // 1. Add AddressConverter to the internal cache.
            EnhancedType.of(Address.class), new AddressConverter());

    public static CustomAttributeConverterProvider create() {
        return new CustomAttributeConverterProvider();
    }

    // 2. The enhanced client queries the provider for attribute converters if it
    //    encounters a type that it does not know how to convert.
    @SuppressWarnings("unchecked")
    @Override
    public <T> AttributeConverter<T> converterFor(EnhancedType<T> enhancedType) {
        return (AttributeConverter<T>) converterCache.get(enhancedType);
    }

    // 3. Custom attribute converter
    private class AddressConverter implements AttributeConverter<Address> {
        // 4. Transform an Address object into a DynamoDB map.
        @Override
        public AttributeValue transformFrom(Address address) {

            Map<String, AttributeValue> attributeValueMap = Map.of(
                    "street", AttributeValue.fromS(address.getStreet()),
                    "city", AttributeValue.fromS(address.getCity()),
                    "state", AttributeValue.fromS(address.getState()),
                    "zipCode", AttributeValue.fromS(address.getZipCode()));

            return AttributeValue.fromM(attributeValueMap);
        }

        // 5. Transform the DynamoDB map attribute to an Address oject.
        @Override
        public Address transformTo(AttributeValue attributeValue) {
            Map<String, AttributeValue> m = attributeValue.m();
            Address address = new Address();
            address.setStreet(m.get("street").s());
            address.setCity(m.get("city").s());
            address.setState(m.get("state").s());
            address.setZipCode(m.get("zipCode").s());

            return address;
        }

        @Override
        public EnhancedType<Address> type() {
            return EnhancedType.of(Address.class);
        }

        @Override
        public AttributeValueType attributeValueType() {
            return AttributeValueType.M;
        }
    }
}
```

## `Address` 类
<a name="ddb-en-client-doc-api-convert-address"></a>

```
public class Address {
                  private String street;
                  private String city;
                  private String state;
                  private String zipCode;

                  public Address() {
                  }

                  public String getStreet() {
                  return this.street;
                  }

                  public String getCity() {
                  return this.city;
                  }

                  public String getState() {
                  return this.state;
                  }

                  public String getZipCode() {
                  return this.zipCode;
                  }

                  public void setStreet(String street) {
                  this.street = street;
                  }

                  public void setCity(String city) {
                  this.city = city;
                  }

                  public void setState(String state) {
                  this.state = state;
                  }

                  public void setZipCode(String zipCode) {
                  this.zipCode = zipCode;
                  }
                  }
```

## 提供地址的帮助程序方法
<a name="ddb-en-client-doc-api-convert-helper"></a>

以下帮助程序方法提供的映射使用自定义 `Address` 实例作为值，而不是使用通用 `Map<String, String>` 实例作为值。

```
    private static Map<String, Address> getAddressesForCustomMappingExample() {
        Address homeAddress = new Address();
        homeAddress.setStreet("100 Any Street");
        homeAddress.setCity("Any Town");
        homeAddress.setState("NC");
        homeAddress.setZipCode("00000");

        Address workAddress = new Address();
        workAddress.setStreet("123 Main Street");
        workAddress.setCity("Any Town");
        workAddress.setState("NC");
        workAddress.setZipCode("00000");

        return Map.of("home", homeAddress,
                "work", workAddress);
    }
```

# 在没有 DynamoDB 的情况下使用 `EnhancedDocument`
<a name="ddb-en-client-doc-api-standalone"></a>

尽管您通常使用 `EnhancedDocument` 的实例来读取和写入文档类型的 DynamoDB 项目，但它也可以独立于 DynamoDB 使用。

您可以使用 `EnhancedDocuments` 的功能，在 JSON 字符串或自定义对象之间，执行到 `AttributeValues` 的低级映射的转换，如以下示例所示。

```
    public static void conversionWithoutDynamoDbExample() {
        Address address = new Address();
        address.setCity("my city");
        address.setState("my state");
        address.setStreet("my street");
        address.setZipCode("00000");

        // Build an EnhancedDocument instance for its conversion functionality alone.
        EnhancedDocument addressEnhancedDoc = EnhancedDocument.builder()
                // Important: You must specify attribute converter providers when you build an EnhancedDocument instance not used with a DynamoDB table.
                .attributeConverterProviders(new CustomAttributeConverterProvider(), DefaultAttributeConverterProvider.create())
                .put("addressDoc", address, Address.class)
                .build();

        // Convert address to a low-level item representation.
        final Map<String, AttributeValue> addressAsAttributeMap = addressEnhancedDoc.getMapOfUnknownType("addressDoc");
        logger.info("addressAsAttributeMap: {}", addressAsAttributeMap.toString());

        // Convert address to a JSON string.
        String addressAsJsonString = addressEnhancedDoc.getJson("addressDoc");
        logger.info("addressAsJsonString: {}", addressAsJsonString);
        // Convert addressEnhancedDoc back to an Address instance.
        Address addressConverted =  addressEnhancedDoc.get("addressDoc", Address.class);
        logger.info("addressConverted: {}", addressConverted.toString());
    }

   /* Console output:
          addressAsAttributeMap: {zipCode=AttributeValue(S=00000), state=AttributeValue(S=my state), street=AttributeValue(S=my street), city=AttributeValue(S=my city)}
          addressAsJsonString: {"zipCode":"00000","state":"my state","street":"my street","city":"my city"}
          addressConverted: Address{street='my street', city='my city', state='my state', zipCode='00000'}
   */
```

**注意**  
当您独立于 DynamoDB 表使用增强型文档时，请务必在生成器上明确设置属性转换器提供程序。  
相比之下，当增强型文档与 DynamoDB 表结合使用时，文档表架构会提供转换器提供程序。

# 使用扩展自定义 DynamoDB 增强型客户端操作
<a name="ddb-en-client-extensions"></a>

DynamoDB 增强型客户端 API 支持插件扩展，这些插件扩展提供的功能不仅限于映射操作。扩展使用两种钩子方法在读取和写入操作期间修改数据：
+ `beforeWrite()` - 在写入操作实际执行之前对其进行修改
+ `afterRead()` - 在读取操作完成之后对返回的结果进行修改

某些操作（例如项目更新）同时执行写入和读取，因此会调用两种钩子方法。

## 如何加载扩展
<a name="ddb-en-client-extensions-loading"></a>

按照您在增强型客户端生成器中指定的顺序加载扩展。加载顺序可能很重要，因为一个扩展可以对前一个扩展变换过的值起作用。

默认情况下，增强版客户端会加载两个扩展：
+ `[VersionedRecordExtension](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/extensions/VersionedRecordExtension.html)` - 提供乐观锁
+ `[AtomicCounterExtension](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/extensions/AtomicCounterExtension.html)` - 自动增加计数器属性

您可以使用增强型客户端生成器覆盖默认行为并加载任何扩展。如果您不想使用默认扩展，也可以指定一个都不用。

**重要**  
如果您加载自己的扩展，则增强型客户端不会加载任何默认扩展。如果您想要任一默认扩展提供的行为，则需要将其明确添加到扩展列表中。

以下示例说明如何在 `VersionedRecordExtension` 之后加载名为 `verifyChecksumExtension` 的自定义扩展。本示例中未加载 `AtomicCounterExtension`。

```
DynamoDbEnhancedClientExtension versionedRecordExtension = VersionedRecordExtension.builder().build();

DynamoDbEnhancedClient enhancedClient = 
    DynamoDbEnhancedClient.builder()
                          .dynamoDbClient(dynamoDbClient)
                          .extensions(versionedRecordExtension, verifyChecksumExtension)
                          .build();
```

## 可用的扩展详细信息和配置
<a name="ddb-en-client-extensions-details"></a>

以下部分提供有关 SDK 中每个可用扩展的详细信息。

### 使用 `VersionedRecordExtension` 实施乐观锁
<a name="ddb-en-client-extensions-VRE"></a>

`VersionedRecordExtension` 扩展提供乐观锁，当项目写入数据库时，它会递增和跟踪项目的版本号。如果实际永久保存的项目的版本号与应用程序上次读取的值不匹配，则会在每次写入时添加一个导致写入失败的条件。

#### 配置
<a name="ddb-en-client-extensions-VRE-conf"></a>

要指定使用哪个属性来跟踪项目版本号，请在表架构中标记数字属性。

以下代码段指定 `version` 属性应包含项目版本号。

```
    @DynamoDbVersionAttribute
    public Integer getVersion() {...};
    public void setVersion(Integer version) {...};
```

下面的代码段显示了等效的静态表架构方法。

```
    .addAttribute(Integer.class, a -> a.name("version")
                                       .getter(Customer::getVersion)
                                       .setter(Customer::setVersion)
                                        // Apply the 'version' tag to the attribute.
                                       .tags(VersionedRecordExtension.AttributeTags.versionAttribute())
```

#### 工作原理
<a name="ddb-en-client-extensions-VRE-how-it-works"></a>

使用 `VersionedRecordExtension` 实现的乐观锁机制会对这些 `DynamoDbEnhancedClient` 和 `DynamoDbTable` 方法产生以下影响：

**`putItem`**  
为新项目分配的初始版本值为 0。可以使用 `@DynamoDbVersionAttribute(startAt = X)` 进行配置。

**`updateItem`**  
如果您检索项目，然后更新它的一个或多个属性，并尝试保存所做更改，那么只有在客户端和服务器端的版本号匹配时操作才能成功。  
如果成功，版本号自动加 1。可以使用 `@DynamoDbVersionAttribute(incrementBy = X)` 进行配置。

**`deleteItem`**  
`DynamoDbVersionAttribute` 注释没有效果。删除项目时，必须手动添加条件表达式。  
以下示例添加了一个条件表达式，以确保删除的项目是读取的项目。在以下示例中，`recordVersion` 是 bean 使用 `@DynamoDbVersionAttribute` 进行注释的属性。  

```
// 1. Read the item and get its current version.
Customer item = customerTable.getItem(Key.builder().partitionValue("someId").build());
// `recordVersion` is the bean's attribute that is annotated with `@DynamoDbVersionAttribute`.
AttributeValue currentVersion = item.getRecordVersion();

// 2. Create conditional delete with the `currentVersion` value.
DeleteItemEnhancedRequest deleteItemRequest =
    DeleteItemEnhancedRequest.builder()
       .key(KEY)
       .conditionExpression(Expression.builder()
           .expression("recordVersion = :current_version_value")
           .putExpressionValue(":current_version_value", currentVersion)
           .build()).build();

customerTable.deleteItem(deleteItemRequest);
```

**`transactWriteItems`**  
+ `addPutItem`：此方法的行为与 `putItem` 相同。
+ `addUpdateItem`：此方法的行为与 `updateItem` 相同。
+ `addDeleteItem`：此方法的行为与 `deleteItem` 相同。

**`batchWriteItem`**  
+ `addPutItem`：此方法的行为与 `putItem` 相同。
+ `addDeleteItem`：此方法的行为与 `deleteItem` 相同。

**注意**  
DynamoDB 全局表在处理并发更新时，使用[‘最后一个写入方为准’协调](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/V2globaltables_HowItWorks.html#V2globaltables_HowItWorks.consistency-modes)策略，DynamoDB 会尽最大努力判断谁是最后一个写入方。如果您使用全局表，这个“最后一个写入方为准”策略意味着锁定策略可能无法按预期工作，因为所有副本最终会基于 DynamoDB 确定的最后一次写入结果达成一致。

#### 如何禁用
<a name="ddb-en-client-extensions-VRE-how-to-disable"></a>

要禁用乐观锁，请不要使用 `@DynamoDbVersionAttribute` 注释。

### 使用 `AtomicCounterExtension` 实施计数器
<a name="ddb-en-client-extensions-ACE"></a>

每次向数据库写入记录时，`AtomicCounterExtension` 扩展都会增加一个带标签的数字属性。您可以指定起始值和增量值。如果未指定任何值，则将起始值设置为 0，属性的值以 1 为增量。

#### 配置
<a name="ddb-en-client-extensions-ACE-conf"></a>

要指定哪个属性是计数器，请在表架构中标记一个类型为 `Long` 的属性。

以下代码段显示了为 `counter` 属性使用默认起始值和增量值的情况。

```
    @DynamoDbAtomicCounter
    public Long getCounter() {...};
    public void setCounter(Long counter) {...};
```

下面的代码段显示了静态表架构方法。原子计数器扩展使用起始值 10，并在每次写入记录时将该值递增 5。

```
    .addAttribute(Integer.class, a -> a.name("counter")
                                       .getter(Customer::getCounter)
                                       .setter(Customer::setCounter)
                                        // Apply the 'atomicCounter' tag to the attribute with start and increment values.
                                       .tags(StaticAttributeTags.atomicCounter(10L, 5L))
```

### 使用 `AutoGeneratedTimestampRecordExtension` 添加时间戳
<a name="ddb-en-client-extensions-AGTE"></a>

每次成功将项目写入数据库时，`AutoGeneratedTimestampRecordExtension` 扩展都会自动使用当前时间戳更新类型为 `[Instant](https://docs.oracle.com/javase/8/docs/api/java/time/Instant.html)` 的已标记属性。默认情况下不加载此扩展。

#### 配置
<a name="ddb-en-client-extensions-AGTE-conf"></a>

要指定将使用当前时间戳更新的属性，请在表架构中标记 `Instant` 属性。

在以下代码段中，`lastUpdate` 属性是扩展行为的目标。请注意该属性必须是 `Instant` 类型的要求。

```
    @DynamoDbAutoGeneratedTimestampAttribute
    public Instant getLastUpdate() {...}
    public void setLastUpdate(Instant lastUpdate) {...}
```

下面的代码段显示了等效的静态表架构方法。

```
     .addAttribute(Instant.class, a -> a.name("lastUpdate")
                                        .getter(Customer::getLastUpdate)
                                        .setter(Customer::setLastUpdate)
                                        // Applying the 'autoGeneratedTimestamp' tag to the attribute.
                                        .tags(AutoGeneratedTimestampRecordExtension.AttributeTags.autoGeneratedTimestampAttribute())
```

### 使用生成一个 UUID AutoGeneratedUuidExtension
<a name="ddb-en-client-extensions-AGUE"></a>

向数据库写入新记录时，`AutoGeneratedUuidExtension` 扩展为属性生成唯一的 UUID（通用唯一标识符）。使用 Java JDK [UUID.randomUUID()](https://docs.oracle.com/javase/8/docs/api/java/util/UUID.html#randomUUID--) 方法并应用于 `java.lang.String` 类型的属性。默认情况下不加载此扩展。

#### 配置
<a name="ddb-en-client-extensions-AGUE-conf"></a>

在以下代码段中，`uniqueId` 属性是扩展行为的目标。

```
    @AutoGeneratedUuidExtension
    public String getUniqueId() {...}
    public void setUniqueId(String uniqueId) {...}
```

下面的代码段显示了等效的静态表架构方法。

```
     .addAttribute(String.class, a -> a.name("uniqueId")
                                        .getter(Customer::getUniqueId)
                                        .setter(Customer::setUniqueId)
                                        // Applying the 'autoGeneratedUuid' tag to the attribute.
                                        .tags(AutoGeneratedUuidExtension.AttributeTags.autoGeneratedUuidAttribute())
```

如果您希望扩展仅为 `putItem` 方法填充 UUID，而不为 `updateItem` 方法填充 UUID，请添加[更新行为](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/mapper/UpdateBehavior.html)注释，如以下代码段所示。

```
    @AutoGeneratedUuidExtension
    @DynamoDbUpdateBehavior(UpdateBehavior.WRITE_IF_NOT_EXISTS)
    public String getUniqueId() {...}
    public void setUniqueId(String uniqueId) {...}
```

如果您使用静态表架构方法，请使用以下等效代码。

```
     .addAttribute(String.class, a -> a.name("uniqueId")
                                        .getter(Customer::getUniqueId)
                                        .setter(Customer::setUniqueId)
                                        // Applying the 'autoGeneratedUuid' tag to the attribute.
                                        .tags(AutoGeneratedUuidExtension.AttributeTags.autoGeneratedUuidAttribute(),
                                              StaticAttributeTags.updateBehavior(UpdateBehavior.WRITE_IF_NOT_EXISTS))
```

# 自定义扩展示例
<a name="ddb-en-client-extensions-custom"></a>

您可以通过实施 `DynamoDbEnhancedClientExtension` 接口来创建自定义扩展。以下自定义扩展类展示了 `beforeWrite()` 方法，如果数据库中的项目还没有 `registrationDate` 属性，则使用更新表达式来设置该属性。

```
public final class CustomExtension implements DynamoDbEnhancedClientExtension {

    // 1. In a custom extension, use an UpdateExpression to define what action to take before
    //    an item is updated.
    @Override
    public WriteModification beforeWrite(DynamoDbExtensionContext.BeforeWrite context) {
        if ( context.operationContext().tableName().equals("Customer")
                && context.operationName().equals(OperationName.UPDATE_ITEM)) {
            return WriteModification.builder()
                    .updateExpression(createUpdateExpression())
                    .build();
        }
        return WriteModification.builder().build();  // Return an "empty" WriteModification instance if the extension should not be applied.
                                                     // In this case, if the code is not updating an item on the Customer table.
    }

    private static UpdateExpression createUpdateExpression() {

        // 2. Use a SetAction, a subclass of UpdateAction, to provide the values in the update.
        SetAction setAction =
                SetAction.builder()
                        .path("registrationDate")
                        .value("if_not_exists(registrationDate, :regValue)")
                        .putExpressionValue(":regValue", AttributeValue.fromS(Instant.now().toString()))
                        .build();
        // 3. Build the UpdateExpression with one or more UpdateAction.
        return UpdateExpression.builder()
                .addAction(setAction)
                .build();
    }
}
```

# 异步使用 DynamoDB 增强型客户端 API
<a name="ddb-en-client-async"></a>

如果您的应用程序需要对 DynamoDB 进行非阻塞异步调用，则可以使用 [DynamoDbEnhancedAsyncClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedAsyncClient.html)。它与同步实现类似，但有以下主要区别：

1. 构建时 `DynamoDbEnhancedAsyncClient`，必须提供标准客户端的异步版本 `DynamoDbAsyncClient`，如以下代码段所示。

   ```
    DynamoDbEnhancedAsyncClient enhancedClient = 
        DynamoDbEnhancedAsyncClient.builder()
                                   .dynamoDbClient(dynamoDbAsyncClient)
                                   .build();
   ```

1. 返回单个数据对象的方法会返回结果的 `CompletableFuture`，而不仅仅是结果。然后，您的应用程序可以执行其他工作，而不必因结果阻塞。以下代码段显示了异步 `getItem()` 方法。

   ```
   CompletableFuture<Customer> result = customerDynamoDbTable.getItem(customer);
   // Perform other work here.
   return result.join();   // Now block and wait for the result.
   ```

1. 返回分页结果列表的方法会返回 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/async/SdkPublisher.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/async/SdkPublisher.html)，而不是同步 `DynamoDbEnhanceClient` 为相同方法返回的 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/pagination/sync/SdkIterable.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/pagination/sync/SdkIterable.html)。然后，您的应用程序可以向该发布者订阅处理程序，以异步方式处理结果，而无需阻塞。

   ```
   PagePublisher<Customer> results = customerDynamoDbTable.query(r -> r.queryConditional(keyEqualTo(k -> k.partitionValue("Smith"))));
   results.subscribe(myCustomerResultsProcessor);
   // Perform other work and let the processor handle the results asynchronously.
   ```

   有关更完整的 `SdkPublisher API` 使用示例，请参阅本指南中讨论异步 `scan()` 方法的部分中的[示例](ddb-en-client-use-multirecord.md#ddb-en-client-use-multirecord-scan-async)。

# 数据类注释
<a name="ddb-en-client-anno-index"></a>

下表列出了可用于数据类的注释，并提供了指向本指南中信息和示例的链接。该表按注释名称的字母升序排序。


**本指南中使用的数据类注释**  

| 注释名称 | 注释适用于1 | 作用 | 本指南中显示的位置 | 
| --- | --- | --- | --- | 
| DynamoDbAtomicCounter | 属性2 | 每次向数据库写入记录时都会增加一个带标签的数字属性。 | [介绍和讨论。](ddb-en-client-extensions.md#ddb-en-client-extensions-ACE) | 
| DynamoDbAttribute | 属性 | 定义或重命名映射到 DynamoDB 表属性的 Bean 属性。 |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_cn/sdk-for-java/latest/developer-guide/ddb-en-client-anno-index.html)  | 
| DynamoDbAutoGeneratedTimestampAttribute | 属性 | 每次成功将项目写入数据库时，都使用当前时间戳更新已标记的属性 | [介绍和讨论](ddb-en-client-extensions.md#ddb-en-client-extensions-AGTE)。 | 
| DynamoDbAutoGeneratedUuid | 属性 | 向数据库写入新记录时，为属性生成唯一的 UUID（通用唯一标识符）。 | [介绍和讨论。](ddb-en-client-extensions.md#ddb-en-client-extensions-AGUE) | 
| DynamoDbBean | class | 将数据类标记为可映射到表架构。 | 第一次是在“入门”部分的 [Customer 类](ddb-en-client-gs-tableschema.md#ddb-en-client-gs-tableschema-anno-bean-cust)上使用。本指南中展示了几种用法。 | 
| DynamoDbConvertedBy | 属性 | 将自定义 AttributeConverter 与带注释的属性相关联。 | [初步讨论和示例。](ddb-en-client-adv-features-conversion.md#ddb-en-client-adv-features-conversion-single) | 
| DynamoDbFlatten | 属性 | 扁平化单独的 DynamoDB 数据类的所有属性，并将它们作为顶级属性添加到从数据库读取的记录和写入数据库的记录中。 |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_cn/sdk-for-java/latest/developer-guide/ddb-en-client-anno-index.html)  | 
| DynamoDbIgnore | 属性 |  导致属性保持未映射状态。  |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_cn/sdk-for-java/latest/developer-guide/ddb-en-client-anno-index.html)  | 
| DynamoDbIgnoreNulls | 属性 | 防止保存嵌套 DynamoDb 对象的空属性。 | [讨论和示例。](ddb-en-client-adv-features-ignore-null.md) | 
| DynamoDbImmutable | class |  将不可变数据类标记为可映射到表架构。  |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_cn/sdk-for-java/latest/developer-guide/ddb-en-client-anno-index.html)  | 
| DynamoDbPartitionKey | 属性 |  将属性标记为 DynamoDb 表的主分区键（哈希键）。  |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_cn/sdk-for-java/latest/developer-guide/ddb-en-client-anno-index.html)  | 
| DynamoDbPreserveEmptyObject | 属性 |  如果映射到带注释的属性的对象没有数据，则指定应使用所有空字段初始化该对象。  | [讨论和示例。](ddb-en-client-adv-features-empty.md) | 
| DynamoDbSecondaryPartitionKey | 属性 |  将属性标记为全局二级属性的分区键。  |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_cn/sdk-for-java/latest/developer-guide/ddb-en-client-anno-index.html)  | 
| DynamoDbSecondarySortKey | 属性 |  将属性标记为全局或本地二级索引的可选排序键。  |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_cn/sdk-for-java/latest/developer-guide/ddb-en-client-anno-index.html)  | 
| DynamoDbSortKey | 属性 |  将属性标记为可选的主排序键（范围键）。  |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_cn/sdk-for-java/latest/developer-guide/ddb-en-client-anno-index.html)  | 
| DynamoDbUpdateBehavior | 属性 |  指定在“更新”操作（例如 UpdateItem）中更新此属性时的行为。  | [简介和示例。](ddb-en-client-adv-features-upd-behavior.md) | 
| DynamoDbVersionAttribute | 属性 | 递增项目版本号。 | [介绍和讨论。](ddb-en-client-extensions.md#ddb-en-client-extensions-VRE) | 

1您可以对 getter 或 setter 应用属性级注释，但不能同时应用两者。本指南显示了有关 getter 的注释。

2对于封装在 JavaBean 数据类中的值，通常使用术语 `property`。但是，为了与 DynamoDB 使用的术语保持一致，本指南（英文版）改用术语 `attribute`。（中文版中，两者的翻译均为“属性”。）

# 使用 Amazon EC2
<a name="examples-ec2"></a>

本部分提供使用适用于 Java 的 AWS SDK 2.x 对 [Amazon EC2](https://docs.aws.amazon.com/ec2/) 进行编程的示例。

**Topics**
+ [管理 Amazon EC2 实例](examples-ec2-instances.md)
+ [使用 AWS 区域 区和可用区](examples-ec2-regions-zones.md)
+ [在 Amazon EC2 中使用安全组](examples-ec2-security-groups.md)
+ [使用 Amazon EC2 实例元数据](examples-ec2-IMDS.md)

# 管理 Amazon EC2 实例
<a name="examples-ec2-instances"></a>

## 创建实例
<a name="create-an-instance"></a>

通过调用 [Ec2Client](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/Ec2Client.html) 的[https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/Ec2Client.html#runInstances(software.amazon.awssdk.services.ec2.model.RunInstancesRequest)](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/Ec2Client.html#runInstances(software.amazon.awssdk.services.ec2.model.RunInstancesRequest))方法创建一个新 Amazon EC2 实例，为其提供[RunInstancesRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/model/RunInstancesRequest.html)包含要使用的[亚马逊系统映像 (AMI)](https://docs.aws.amazon.com//AWSEC2/latest/UserGuide/AMIs.html) 和[实例](https://docs.aws.amazon.com//AWSEC2/latest/UserGuide/instance-types.html)类型。

 **导入** 

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.ec2.Ec2Client;
import software.amazon.awssdk.services.ec2.model.InstanceType;
import software.amazon.awssdk.services.ec2.model.RunInstancesRequest;
import software.amazon.awssdk.services.ec2.model.RunInstancesResponse;
import software.amazon.awssdk.services.ec2.model.Tag;
import software.amazon.awssdk.services.ec2.model.CreateTagsRequest;
import software.amazon.awssdk.services.ec2.model.Ec2Exception;
```

 **代码** 

```
   public static String createEC2Instance(Ec2Client ec2,String name, String amiId ) {

        RunInstancesRequest runRequest = RunInstancesRequest.builder()
                .imageId(amiId)
                .instanceType(InstanceType.T1_MICRO)
                .maxCount(1)
                .minCount(1)
                .build();

        RunInstancesResponse response = ec2.runInstances(runRequest);
        String instanceId = response.instances().get(0).instanceId();

        Tag tag = Tag.builder()
                .key("Name")
                .value(name)
                .build();

        CreateTagsRequest tagRequest = CreateTagsRequest.builder()
                .resources(instanceId)
                .tags(tag)
                .build();

        try {
            ec2.createTags(tagRequest);
            System.out.printf(
                    "Successfully started EC2 Instance %s based on AMI %s",
                    instanceId, amiId);

          return instanceId;

        } catch (Ec2Exception e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }

        return "";
    }
```

请参阅上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/ac748d8ef99cd17e297cb74fe13aa671e2679088/javav2/example_code/ec2/src/main/java/com/example/ec2/CreateInstance.java) GitHub。

## 启动实例
<a name="start-an-instance"></a>

要启动 Amazon EC2 实例，请调用 Ec2Client [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/Ec2Client.html#startInstances(software.amazon.awssdk.services.ec2.model.StartInstancesRequest)](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/Ec2Client.html#startInstances(software.amazon.awssdk.services.ec2.model.StartInstancesRequest))的方法，为其提供[StartInstancesRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/model/StartInstancesRequest.html)包含要启动的实例的 ID。

 **导入** 

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.ec2.Ec2Client;
import software.amazon.awssdk.services.ec2.model.StartInstancesRequest;
import software.amazon.awssdk.services.ec2.model.StopInstancesRequest;
```

 **代码** 

```
    public static void startInstance(Ec2Client ec2, String instanceId) {

        StartInstancesRequest request = StartInstancesRequest.builder()
                .instanceIds(instanceId)
                .build();

        ec2.startInstances(request);
        System.out.printf("Successfully started instance %s", instanceId);
    }
```

请参阅上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/ac748d8ef99cd17e297cb74fe13aa671e2679088/javav2/example_code/ec2/src/main/java/com/example/ec2/StartStopInstance.java) GitHub。

## 停止实例
<a name="stop-an-instance"></a>

要停止 Amazon EC2 实例，请调用 Ec2Client [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/Ec2Client.html#stopInstances(software.amazon.awssdk.services.ec2.model.StopInstancesRequest)](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/Ec2Client.html#stopInstances(software.amazon.awssdk.services.ec2.model.StopInstancesRequest))的方法，为其提供[StopInstancesRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/model/StopInstancesRequest.html)包含要停止的实例的 ID。

 **导入** 

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.ec2.Ec2Client;
import software.amazon.awssdk.services.ec2.model.StartInstancesRequest;
import software.amazon.awssdk.services.ec2.model.StopInstancesRequest;
```

 **代码** 

```
    public static void stopInstance(Ec2Client ec2, String instanceId) {

        StopInstancesRequest request = StopInstancesRequest.builder()
                .instanceIds(instanceId)
                .build();

        ec2.stopInstances(request);
        System.out.printf("Successfully stopped instance %s", instanceId);
    }
```

请参阅上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/ac748d8ef99cd17e297cb74fe13aa671e2679088/javav2/example_code/ec2/src/main/java/com/example/ec2/StartStopInstance.java) GitHub。

## 重启实例
<a name="reboot-an-instance"></a>

要重启 Amazon EC2 实例，请调用 Ec2Client [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/Ec2Client.html#rebootInstances(software.amazon.awssdk.services.ec2.model.RebootInstancesRequest)](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/Ec2Client.html#rebootInstances(software.amazon.awssdk.services.ec2.model.RebootInstancesRequest))的方法，为其提供[RebootInstancesRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/model/RebootInstancesRequest.html)包含要重启的实例 ID。

 **导入** 

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.ec2.Ec2Client;
import software.amazon.awssdk.services.ec2.model.Ec2Exception;
import software.amazon.awssdk.services.ec2.model.RebootInstancesRequest;
```

 **代码** 

```
    public static void rebootEC2Instance(Ec2Client ec2, String instanceId) {

      try {
            RebootInstancesRequest request = RebootInstancesRequest.builder()
                .instanceIds(instanceId)
                    .build();

            ec2.rebootInstances(request);
            System.out.printf(
                "Successfully rebooted instance %s", instanceId);
    } catch (Ec2Exception e) {
          System.err.println(e.awsErrorDetails().errorMessage());
          System.exit(1);
     }
  }
```

请参阅上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/ac748d8ef99cd17e297cb74fe13aa671e2679088/javav2/example_code/ec2/src/main/java/com/example/ec2/RebootInstance.java) GitHub。

## 描述实例
<a name="describe-instances"></a>

要列出您的实例，请创建[DescribeInstancesRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/model/DescribeInstancesRequest.html)并调用 Ec2Client [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/Ec2Client.html#describeInstances(software.amazon.awssdk.services.ec2.model.DescribeInstancesRequest)](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/Ec2Client.html#describeInstances(software.amazon.awssdk.services.ec2.model.DescribeInstancesRequest))的方法。它将返回一个[DescribeInstancesResponse](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/model/DescribeInstancesResponse.html)对象，您可以使用该对象列出您的账户和地区的 Amazon EC2 实例。

实例按*预留*进行分组。每个预留对应对启动实例的 `startInstances` 的调用。要列出您的实例，您必须先调用 `DescribeInstancesResponse` 类的 `reservations` 方法，然后在每个返回的 `instances`Reservation[ 对象上调用 ](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/model/Reservation.html)。

 **导入** 

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.ec2.Ec2Client;
import software.amazon.awssdk.services.ec2.model.DescribeInstancesRequest;
import software.amazon.awssdk.services.ec2.model.DescribeInstancesResponse;
import software.amazon.awssdk.services.ec2.model.Instance;
import software.amazon.awssdk.services.ec2.model.Reservation;
import software.amazon.awssdk.services.ec2.model.Ec2Exception;
```

 **代码** 

```
    public static void describeEC2Instances( Ec2Client ec2){

        String nextToken = null;

        try {

            do {
                DescribeInstancesRequest request = DescribeInstancesRequest.builder().maxResults(6).nextToken(nextToken).build();
                DescribeInstancesResponse response = ec2.describeInstances(request);

                for (Reservation reservation : response.reservations()) {
                    for (Instance instance : reservation.instances()) {
                        System.out.println("Instance Id is " + instance.instanceId());
                        System.out.println("Image id is "+  instance.imageId());
                        System.out.println("Instance type is "+  instance.instanceType());
                        System.out.println("Instance state name is "+  instance.state().name());
                        System.out.println("monitoring information is "+  instance.monitoring().state());

                }
            }
                nextToken = response.nextToken();
            } while (nextToken != null);

        } catch (Ec2Exception e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
    }
```

结果将分页；您可以获取更多结果，方法是将从结果对象的 `nextToken` 方法返回的值传递到新请求对象的 `nextToken` 方法，然后在下一个 `describeInstances` 调用中使用新请求对象。

请参阅上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/ac748d8ef99cd17e297cb74fe13aa671e2679088/javav2/example_code/ec2/src/main/java/com/example/ec2/DescribeInstances.java) GitHub。

## 监控实例
<a name="monitor-an-instance"></a>

您可以监控 Amazon EC2 实例的各个方面，例如 CPU 和网络利用率、可用内存和剩余磁盘空间。要了解有关实例监控的更多信息，请参阅 Linux 实例 Amazon EC2 用户指南 Amazon EC2中的[监控](https://docs.aws.amazon.com//AWSEC2/latest/UserGuide/monitoring_ec2.html)。

要开始监控实例，您必须[MonitorInstancesRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/model/MonitorInstancesRequest.html)使用要监控的实例的 ID 创建一个，并将其传递给 Ec2Client [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/Ec2Client.html#monitorInstances(software.amazon.awssdk.services.ec2.model.MonitorInstancesRequest)](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/Ec2Client.html#monitorInstances(software.amazon.awssdk.services.ec2.model.MonitorInstancesRequest))的方法。

 **导入** 

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.ec2.Ec2Client;
import software.amazon.awssdk.services.ec2.model.MonitorInstancesRequest;
import software.amazon.awssdk.services.ec2.model.UnmonitorInstancesRequest;
```

 **代码** 

```
    public static void monitorInstance( Ec2Client ec2, String instanceId) {

        MonitorInstancesRequest request = MonitorInstancesRequest.builder()
                .instanceIds(instanceId).build();

        ec2.monitorInstances(request);
        System.out.printf(
                "Successfully enabled monitoring for instance %s",
                instanceId);
    }
```

请参阅上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/ac748d8ef99cd17e297cb74fe13aa671e2679088/javav2/example_code/ec2/src/main/java/com/example/ec2/MonitorInstance.java) GitHub。

## 停止实例监控
<a name="stop-instance-monitoring"></a>

要停止监控实例，请[UnmonitorInstancesRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/model/UnmonitorInstancesRequest.html)使用要停止监控的实例 ID 创建一个，然后将其传递给 Ec2Client [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/Ec2Client.html#unmonitorInstances(software.amazon.awssdk.services.ec2.model.UnmonitorInstancesRequest)](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/Ec2Client.html#unmonitorInstances(software.amazon.awssdk.services.ec2.model.UnmonitorInstancesRequest))的方法。

 **导入** 

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.ec2.Ec2Client;
import software.amazon.awssdk.services.ec2.model.MonitorInstancesRequest;
import software.amazon.awssdk.services.ec2.model.UnmonitorInstancesRequest;
```

 **代码** 

```
    public static void unmonitorInstance(Ec2Client ec2, String instanceId) {
        UnmonitorInstancesRequest request = UnmonitorInstancesRequest.builder()
                .instanceIds(instanceId).build();

        ec2.unmonitorInstances(request);

        System.out.printf(
                "Successfully disabled monitoring for instance %s",
                instanceId);
    }
```

请参阅上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/ac748d8ef99cd17e297cb74fe13aa671e2679088/javav2/example_code/ec2/src/main/java/com/example/ec2/MonitorInstance.java) GitHub。

## 更多信息
<a name="more-information"></a>
+  [RunInstances](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_RunInstances.html)在 Amazon EC2 API 参考中
+  [DescribeInstances](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeInstances.html)在 Amazon EC2 API 参考中
+  [StartInstances](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_StartInstances.html)在 Amazon EC2 API 参考中
+  [StopInstances](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_StopInstances.html)在 Amazon EC2 API 参考中
+  [RebootInstances](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_RebootInstances.html)在 Amazon EC2 API 参考中
+  [MonitorInstances](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_MonitorInstances.html)在 Amazon EC2 API 参考中
+  [UnmonitorInstances](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_UnmonitorInstances.html)在 Amazon EC2 API 参考中

# 使用 AWS 区域 区和可用区
<a name="examples-ec2-regions-zones"></a>

## 描述区域
<a name="describe-regions"></a>

要列出账户可用的区域，请调用 Ec2Client 的 `describeRegions` 方法。它返回 [DescribeRegionsResponse](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/model/DescribeRegionsResponse.html)。调用返回对象的 `regions` 方法，获取表示各个区域的 [Region](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/model/Region.html) 对象的列表。

 **导入** 

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.ec2.Ec2AsyncClient;
import software.amazon.awssdk.services.ec2.model.DescribeRegionsResponse;
import software.amazon.awssdk.services.ec2.model.DescribeAvailabilityZonesResponse;
import java.util.concurrent.CompletableFuture;
```

 **代码** 

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.ec2.Ec2AsyncClient;
import software.amazon.awssdk.services.ec2.model.DescribeRegionsResponse;
import software.amazon.awssdk.services.ec2.model.DescribeAvailabilityZonesResponse;
import java.util.concurrent.CompletableFuture;

/**
 * Before running this Java V2 code example, set up your development
 * environment, including your credentials.
 *
 * For more information, see the following documentation topic:
 *
 * https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/get-started.html
 */
public class DescribeRegionsAndZones {
    public static void main(String[] args) {
        Ec2AsyncClient ec2AsyncClient = Ec2AsyncClient.builder()
            .region(Region.US_EAST_1)
            .build();

        try {
            CompletableFuture<Void> future = describeEC2RegionsAndZonesAsync(ec2AsyncClient);
            future.join(); // Wait for both async operations to complete.
        } catch (RuntimeException rte) {
            System.err.println("An exception occurred: " + (rte.getCause() != null ? rte.getCause().getMessage() : rte.getMessage()));
        }
    }

    /**
     * Asynchronously describes the EC2 regions and availability zones.
     *
     * @param ec2AsyncClient the EC2 async client used to make the API calls
     * @return a {@link CompletableFuture} that completes when both the region and availability zone descriptions are complete
     */
    public static CompletableFuture<Void> describeEC2RegionsAndZonesAsync(Ec2AsyncClient ec2AsyncClient) {
        // Initiate the asynchronous request to describe regions
        CompletableFuture<DescribeRegionsResponse> regionsResponse = ec2AsyncClient.describeRegions();

        // Handle the response or exception for regions
        CompletableFuture<DescribeRegionsResponse> regionsFuture = regionsResponse.whenComplete((regionsResp, ex) -> {
            if (ex != null) {
                // Handle the exception by throwing a RuntimeException
                throw new RuntimeException("Failed to describe EC2 regions.", ex);
            } else if (regionsResp == null || regionsResp.regions().isEmpty()) {
                // Throw an exception if the response is null or the result is empty
                throw new RuntimeException("No EC2 regions found.");
            } else {
                // Process the response if no exception occurred and the result is not empty
                regionsResp.regions().forEach(region -> {
                    System.out.printf(
                        "Found Region %s with endpoint %s%n",
                        region.regionName(),
                        region.endpoint());
                });
            }
        });

        CompletableFuture<DescribeAvailabilityZonesResponse> zonesResponse = ec2AsyncClient.describeAvailabilityZones();
        CompletableFuture<DescribeAvailabilityZonesResponse> zonesFuture = zonesResponse.whenComplete((zonesResp, ex) -> {
            if (ex != null) {
                throw new RuntimeException("Failed to describe EC2 availability zones.", ex);
            } else if (zonesResp == null || zonesResp.availabilityZones().isEmpty()) {
                throw new RuntimeException("No EC2 availability zones found.");
            } else {
                zonesResp.availabilityZones().forEach(zone -> {
                    System.out.printf(
                        "Found Availability Zone %s with status %s in region %s%n",
                        zone.zoneName(),
                        zone.state(),
                        zone.regionName()
                    );
                });
            }
        });

        return CompletableFuture.allOf(regionsFuture, zonesFuture);
    }
}
```

请参阅上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/7486a1a092aa8e16a21698ef26f9d524fef62e55/javav2/example_code/ec2/src/main/java/com/example/ec2/DescribeRegionsAndZones.java) GitHub。

## 描述可用区
<a name="describe-availability-zones"></a>

要列出账户可用的每个可用区，请调用 Ec2Client 的 `describeAvailabilityZones` 方法。它返回 [DescribeAvailabilityZonesResponse](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/model/DescribeAvailabilityZonesResponse.html)。调用其`availabilityZones`方法以获取代表每个可用区的[AvailabilityZone](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/model/AvailabilityZone.html)对象列表。

 **导入** 

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.ec2.Ec2AsyncClient;
import software.amazon.awssdk.services.ec2.model.DescribeRegionsResponse;
import software.amazon.awssdk.services.ec2.model.DescribeAvailabilityZonesResponse;
import java.util.concurrent.CompletableFuture;
```

 **代码** 

创建 Ec2Client。

```
        Ec2AsyncClient ec2AsyncClient = Ec2AsyncClient.builder()
            .region(Region.US_EAST_1)
            .build();
```

然后调用 describeAvailabilityZones () 并检索结果。

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.ec2.Ec2AsyncClient;
import software.amazon.awssdk.services.ec2.model.DescribeRegionsResponse;
import software.amazon.awssdk.services.ec2.model.DescribeAvailabilityZonesResponse;
import java.util.concurrent.CompletableFuture;

/**
 * Before running this Java V2 code example, set up your development
 * environment, including your credentials.
 *
 * For more information, see the following documentation topic:
 *
 * https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/get-started.html
 */
public class DescribeRegionsAndZones {
    public static void main(String[] args) {
        Ec2AsyncClient ec2AsyncClient = Ec2AsyncClient.builder()
            .region(Region.US_EAST_1)
            .build();

        try {
            CompletableFuture<Void> future = describeEC2RegionsAndZonesAsync(ec2AsyncClient);
            future.join(); // Wait for both async operations to complete.
        } catch (RuntimeException rte) {
            System.err.println("An exception occurred: " + (rte.getCause() != null ? rte.getCause().getMessage() : rte.getMessage()));
        }
    }

    /**
     * Asynchronously describes the EC2 regions and availability zones.
     *
     * @param ec2AsyncClient the EC2 async client used to make the API calls
     * @return a {@link CompletableFuture} that completes when both the region and availability zone descriptions are complete
     */
    public static CompletableFuture<Void> describeEC2RegionsAndZonesAsync(Ec2AsyncClient ec2AsyncClient) {
        // Initiate the asynchronous request to describe regions
        CompletableFuture<DescribeRegionsResponse> regionsResponse = ec2AsyncClient.describeRegions();

        // Handle the response or exception for regions
        CompletableFuture<DescribeRegionsResponse> regionsFuture = regionsResponse.whenComplete((regionsResp, ex) -> {
            if (ex != null) {
                // Handle the exception by throwing a RuntimeException
                throw new RuntimeException("Failed to describe EC2 regions.", ex);
            } else if (regionsResp == null || regionsResp.regions().isEmpty()) {
                // Throw an exception if the response is null or the result is empty
                throw new RuntimeException("No EC2 regions found.");
            } else {
                // Process the response if no exception occurred and the result is not empty
                regionsResp.regions().forEach(region -> {
                    System.out.printf(
                        "Found Region %s with endpoint %s%n",
                        region.regionName(),
                        region.endpoint());
                });
            }
        });

        CompletableFuture<DescribeAvailabilityZonesResponse> zonesResponse = ec2AsyncClient.describeAvailabilityZones();
        CompletableFuture<DescribeAvailabilityZonesResponse> zonesFuture = zonesResponse.whenComplete((zonesResp, ex) -> {
            if (ex != null) {
                throw new RuntimeException("Failed to describe EC2 availability zones.", ex);
            } else if (zonesResp == null || zonesResp.availabilityZones().isEmpty()) {
                throw new RuntimeException("No EC2 availability zones found.");
            } else {
                zonesResp.availabilityZones().forEach(zone -> {
                    System.out.printf(
                        "Found Availability Zone %s with status %s in region %s%n",
                        zone.zoneName(),
                        zone.state(),
                        zone.regionName()
                    );
                });
            }
        });

        return CompletableFuture.allOf(regionsFuture, zonesFuture);
    }
}
```

请参阅上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/7486a1a092aa8e16a21698ef26f9d524fef62e55/javav2/example_code/ec2/src/main/java/com/example/ec2/DescribeRegionsAndZones.java) GitHub。

## 描述账户
<a name="describe-accounts"></a>

要列出有关您账户的 EC2相关信息，请调用 Ec2Client `describeAccountAttributes` 的方法。此方法返回一个 [DescribeAccountAttributesResponse](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/model/DescribeAccountAttributesResponse.html) 对象。调用此对象`accountAttributes`方法以获取[AccountAttribute](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/model/AccountAttribute.html)对象列表。您可以遍历该列表来检索 `AccountAttribute` 对象。

您可以通过调用 `AccountAttribute` 对象的 `attributeValues` 方法来获取您账户的属性值。此方法返回[AccountAttributeValue](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/model/AccountAttributeValue.html)对象列表。您可以遍历第二个列表来显示属性的值（请参阅以下代码示例）。

 **导入** 

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.ec2.Ec2AsyncClient;
import software.amazon.awssdk.services.ec2.model.DescribeAccountAttributesResponse;
import java.util.concurrent.CompletableFuture;
```

 **代码** 

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.ec2.Ec2AsyncClient;
import software.amazon.awssdk.services.ec2.model.DescribeAccountAttributesResponse;
import java.util.concurrent.CompletableFuture;

/**
 * Before running this Java V2 code example, set up your development
 * environment, including your credentials.
 *
 * For more information, see the following documentation topic:
 *
 * https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/get-started.html
 */
public class DescribeAccount {
    public static void main(String[] args) {
        Ec2AsyncClient ec2AsyncClient = Ec2AsyncClient.builder()
            .region(Region.US_EAST_1)
            .build();

        try {
            CompletableFuture<DescribeAccountAttributesResponse> future = describeEC2AccountAsync(ec2AsyncClient);
            future.join();
            System.out.println("EC2 Account attributes described successfully.");
        } catch (RuntimeException rte) {
            System.err.println("An exception occurred: " + (rte.getCause() != null ? rte.getCause().getMessage() : rte.getMessage()));
        }
    }

    /**
     * Describes the EC2 account attributes asynchronously.
     *
     * @param ec2AsyncClient the EC2 asynchronous client to use for the operation
     * @return a {@link CompletableFuture} containing the {@link DescribeAccountAttributesResponse} with the account attributes
     */
    public static CompletableFuture<DescribeAccountAttributesResponse> describeEC2AccountAsync(Ec2AsyncClient ec2AsyncClient) {
        CompletableFuture<DescribeAccountAttributesResponse> response = ec2AsyncClient.describeAccountAttributes();
        return response.whenComplete((accountResults, ex) -> {
            if (ex != null) {
                // Handle the exception by throwing a RuntimeException.
                throw new RuntimeException("Failed to describe EC2 account attributes.", ex);
            } else if (accountResults == null || accountResults.accountAttributes().isEmpty()) {
                // Throw an exception if the response is null or no account attributes are found.
                throw new RuntimeException("No account attributes found.");
            } else {
                // Process the response if no exception occurred.
                accountResults.accountAttributes().forEach(attribute -> {
                    System.out.println("\nThe name of the attribute is " + attribute.attributeName());
                    attribute.attributeValues().forEach(
                        myValue -> System.out.println("The value of the attribute is " + myValue.attributeValue()));
                });
            }
        });
    }
}
```

请参阅上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/7486a1a092aa8e16a21698ef26f9d524fef62e55/javav2/example_code/ec2/src/main/java/com/example/ec2/DescribeAccount.java) GitHub。

## 更多信息
<a name="more-information"></a>
+  Linux 实例 Amazon EC2 用户指南中的@@ [区域和可用区](https://docs.aws.amazon.com//AWSEC2/latest/UserGuide/using-regions-availability-zones.html)
+  [DescribeRegions](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeRegions.html)在 Amazon EC2 API 参考中
+  [DescribeAvailabilityZones](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeAvailabilityZones.html)在 Amazon EC2 API 参考中

# 在 Amazon EC2 中使用安全组
<a name="examples-ec2-security-groups"></a>

## 创建安全组
<a name="create-a-security-group"></a>

要创建安全组，请使用包含密钥名称的 [CreateSecurityGroupRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/model/CreateSecurityGroupRequest.html) 调用 Ec2Client 的 `createSecurityGroup` 方法。

 **导入**。

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.ec2.Ec2Client;
import software.amazon.awssdk.services.ec2.model.CreateSecurityGroupRequest;
import software.amazon.awssdk.services.ec2.model.AuthorizeSecurityGroupIngressRequest;
import software.amazon.awssdk.services.ec2.model.AuthorizeSecurityGroupIngressResponse;
import software.amazon.awssdk.services.ec2.model.Ec2Exception;
import software.amazon.awssdk.services.ec2.model.IpPermission;
import software.amazon.awssdk.services.ec2.model.CreateSecurityGroupResponse;
import software.amazon.awssdk.services.ec2.model.IpRange;
```

 **代码** 

```
            CreateSecurityGroupRequest createRequest = CreateSecurityGroupRequest.builder()
                .groupName(groupName)
                .description(groupDesc)
                .vpcId(vpcId)
                .build();

            CreateSecurityGroupResponse resp= ec2.createSecurityGroup(createRequest);
```

请参阅 GitHub 上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/0b1785e42949ebf959eaa0f0da4dc2a48f92ea25/javav2/example_code/ec2/src/main/java/com/example/ec2/CreateSecurityGroup.java)。

## 配置安全组
<a name="configure-a-security-group"></a>

安全组可以控制对 Amazon EC2 实例的入站 (入口) 流量和出站 (出口) 流量。

要向安全组添加入口规则，请使用 Ec2Client 的 `authorizeSecurityGroupIngress` 方法，提供安全组的名称和您想要在 [AuthorizeSecurityGroupIngressRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/model/AuthorizeSecurityGroupIngressRequest.html) 对象中分配给安全组的访问规则 ([IpPermission](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/model/IpPermission.html))。以下示例演示如何将 IP 权限添加到安全组。

 **导入**。

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.ec2.Ec2Client;
import software.amazon.awssdk.services.ec2.model.CreateSecurityGroupRequest;
import software.amazon.awssdk.services.ec2.model.AuthorizeSecurityGroupIngressRequest;
import software.amazon.awssdk.services.ec2.model.AuthorizeSecurityGroupIngressResponse;
import software.amazon.awssdk.services.ec2.model.Ec2Exception;
import software.amazon.awssdk.services.ec2.model.IpPermission;
import software.amazon.awssdk.services.ec2.model.CreateSecurityGroupResponse;
import software.amazon.awssdk.services.ec2.model.IpRange;
```

 **代码** 

首先，创建一个 Ec2Client

```
        Region region = Region.US_WEST_2;
        Ec2Client ec2 = Ec2Client.builder()
                .region(region)
                .build();
```

然后使用 Ec2Client 的 `authorizeSecurityGroupIngress` 方法，

```
            IpRange ipRange = IpRange.builder()
                .cidrIp("0.0.0.0/0").build();

            IpPermission ipPerm = IpPermission.builder()
                .ipProtocol("tcp")
                .toPort(80)
                .fromPort(80)
                .ipRanges(ipRange)
                .build();

            IpPermission ipPerm2 = IpPermission.builder()
                .ipProtocol("tcp")
                .toPort(22)
                .fromPort(22)
                .ipRanges(ipRange)
                .build();

            AuthorizeSecurityGroupIngressRequest authRequest =
                AuthorizeSecurityGroupIngressRequest.builder()
                        .groupName(groupName)
                        .ipPermissions(ipPerm, ipPerm2)
                        .build();

            AuthorizeSecurityGroupIngressResponse authResponse =
            ec2.authorizeSecurityGroupIngress(authRequest);

            System.out.printf(
                "Successfully added ingress policy to Security Group %s",
                groupName);

            return resp.groupId();

        } catch (Ec2Exception e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
        return "";
    }
```

要向安全组添加出口规则，请在 [AuthorizeSecurityGroupEgressRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/model/AuthorizeSecurityGroupEgressRequest.html) 中向 Ec2Client 的 `authorizeSecurityGroupEgress` 方法提供相似的数据。

请参阅 GitHub 上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/0b1785e42949ebf959eaa0f0da4dc2a48f92ea25/javav2/example_code/ec2/src/main/java/com/example/ec2/CreateSecurityGroup.java)。

## 描述安全组
<a name="describe-security-groups"></a>

要描述您的安全组或获取相关信息，请调用 Ec2Client 的 `describeSecurityGroups` 方法。该方法返回 [DescribeSecurityGroupsResponse](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/model/DescribeSecurityGroupsResponse.html)，使用它后，您可以通过调用其 `securityGroups` 方法（返回一个 [SecurityGroup](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/model/SecurityGroup.html) 对象的列表）来访问安全组的列表。

 **导入**。

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.ec2.Ec2Client;
import software.amazon.awssdk.services.ec2.model.DescribeSecurityGroupsRequest;
import software.amazon.awssdk.services.ec2.model.DescribeSecurityGroupsResponse;
import software.amazon.awssdk.services.ec2.model.SecurityGroup;
import software.amazon.awssdk.services.ec2.model.Ec2Exception;
```

 **代码** 

```
     public static void describeEC2SecurityGroups(Ec2Client ec2, String groupId) {

        try {
            DescribeSecurityGroupsRequest request =
                DescribeSecurityGroupsRequest.builder()
                        .groupIds(groupId).build();

            DescribeSecurityGroupsResponse response =
                ec2.describeSecurityGroups(request);

             for(SecurityGroup group : response.securityGroups()) {
                System.out.printf(
                    "Found Security Group with id %s, " +
                            "vpc id %s " +
                            "and description %s",
                    group.groupId(),
                    group.vpcId(),
                    group.description());
            }
        } catch (Ec2Exception e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
    }
```

请参阅 GitHub 上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/0b1785e42949ebf959eaa0f0da4dc2a48f92ea25/javav2/example_code/ec2/src/main/java/com/example/ec2/DescribeSecurityGroups.java)。

## 删除安全组
<a name="delete-a-security-group"></a>

要删除安全组，请调用 Ec2Client 的 `deleteSecurityGroup` 方法，将其传递给一个包含要删除安全组 ID 的 [DeleteSecurityGroupRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/ec2/model/DeleteSecurityGroupRequest.html)。

 **导入**。

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.ec2.Ec2Client;
import software.amazon.awssdk.services.ec2.model.DeleteSecurityGroupRequest;
import software.amazon.awssdk.services.ec2.model.Ec2Exception;
```

 **代码** 

```
    public static void deleteEC2SecGroup(Ec2Client ec2,String groupId) {

        try {
            DeleteSecurityGroupRequest request = DeleteSecurityGroupRequest.builder()
                .groupId(groupId)
                .build();

            ec2.deleteSecurityGroup(request);
            System.out.printf(
                "Successfully deleted Security Group with id %s", groupId);

        } catch (Ec2Exception e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
     }
```

请参阅 GitHub 上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/0b1785e42949ebf959eaa0f0da4dc2a48f92ea25/javav2/example_code/ec2/src/main/java/com/example/ec2/DeleteSecurityGroup.java)。

## 更多信息
<a name="more-information"></a>
+  《Amazon EC2 用户指南（适用于 Linux 实例）》中的 [Amazon EC2 安全组](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-security-groups.html)
+  《Amazon EC2 用户指南（适用于 Linux 实例）》中的[为您的 Linux 实例授权入站流量](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/authorizing-access-to-an-instance.html)
+  《Amazon EC2 API Reference》中的 [CreateSecurityGroup](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_CreateSecurityGroup.html)
+  《Amazon EC2 API Reference》中的 [DescribeSecurityGroups](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DescribeSecurityGroups.html)
+  《Amazon EC2 API Reference》中的 [DeleteSecurityGroup](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_DeleteSecurityGroup.html)
+  《Amazon EC2 API Reference》中的 [AuthorizeSecurityGroupIngress](https://docs.aws.amazon.com/AWSEC2/latest/APIReference/API_AuthorizeSecurityGroupIngress.html)

# 使用 Amazon EC2 实例元数据
<a name="examples-ec2-IMDS"></a>

适用于 Amazon EC2 实例元数据服务的 Java SDK 客户端（元数据客户端）允许您的应用程序访问其本地 EC2 实例上的元数据。元数据客户端使用本地实例 [IMDSv2](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html)（实例元数据服务 v2），并使用面向会话的请求。

SDK 中有两个客户端类可用。同步 `[Ec2MetadataClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/imds/Ec2MetadataClient.html)` 用于阻塞操作，[https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/imds/Ec2MetadataAsyncClient.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/imds/Ec2MetadataAsyncClient.html) 用于异步、非阻塞用例。

## 开始使用
<a name="examples-ec2-IMDS-getstarted"></a>

要使用元数据客户端，请将 `imds` Maven 构件添加到您的项目中。您还需要在类路径上具有 `[SdkHttpClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/http/SdkHttpClient.html)` 的类，或对于异步变体而言，具有 `[SdkAsyncHttpClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/http/async/SdkAsyncHttpClient.html)` 的类。

以下 Maven XML 显示了使用同步的依赖关系片段[UrlConnectionHttpClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/http/urlconnection/UrlConnectionHttpClient.html)以及元数据客户端的依赖关系。

```
<dependencyManagement>
   <dependencies>
        <dependency>
            <groupId>software.amazon.awssdk</groupId>
            <artifactId>bom</artifactId>
            <version>VERSION</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>

<dependencies>
    <dependency>
        <groupId>software.amazon.awssdk</groupId>
        <artifactId>imds</artifactId>
    </dependency>
    <dependency>
        <groupId>software.amazon.awssdk</groupId>
        <artifactId>url-connection-client</artifactId>
    </dependency>
    <!-- other dependencies --> 
</dependencies>
```

在 [Maven Central 存储库](https://central.sonatype.com/artifact/software.amazon.awssdk/bom)中搜索 `bom` 构件的最新版本。

要使用异步 HTTP 客户端，请替换 `url-connection-client` 构件的依赖项片段。例如，以下代码段引入了[NettyNioAsyncHttpClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/http/nio/netty/NettyNioAsyncHttpClient.html)实现。

```
    <dependency>
        <groupId>software.amazon.awssdk</groupId>
        <artifactId>netty-nio-client</artifactId>
    </dependency>
```

## 使用元数据客户端
<a name="examples-ec2-IMDS-use"></a>

### 实例化元数据客户端
<a name="examples-ec2-IMDS-use-create"></a>

当类路径上只有一个 `SdkHttpClient` 接口的实现时，您可以实例化同步 `Ec2MetadataClient` 的实例。为此，请调用 static `Ec2MetadataClient#create()` 方法，如以下代码段所示。

```
Ec2MetadataClient client = Ec2MetadataClient.create(); // 'Ec2MetadataAsyncClient#create' is the asynchronous version.
```

如果您的应用程序有多个 `SdkHttpClient` 或 `SdkHttpAsyncClient` 接口的实现，则必须指定一个实现以供元数据客户端使用，如[可配置 HTTP 客户端](#examples-ec2-IMDS-features-http)部分所示。

**注意**  
对于大多数服务客户端（例如 Amazon S3），适用于 Java 的 SDK 会自动添加 `SdkHttpClient` 或 `SdkHttpAsyncClient` 接口的实现。如果您的元数据客户端使用相同的实现，则 `Ec2MetadataClient#create()` 将起作用。如果您需要不同的实现，则必须在创建元数据客户端时指定它。

### 发送请求
<a name="examples-ec2-IMDS-use-req"></a>

要检索实例元数据，请实例化 `EC2MetadataClient` 类，然后使用指定[实例元数据类别](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instancedata-data-categories.html)的路径参数调用 `get` 方法。

以下示例将与 `ami-id` 键关联的值打印到控制台。

```
Ec2MetadataClient client = Ec2MetadataClient.create();
Ec2MetadataResponse response = client.get("/latest/meta-data/ami-id");
System.out.println(response.asString());
client.close(); // Closes the internal resources used by the Ec2MetadataClient class.
```

如果路径无效，则 `get` 方法将引发异常。

请对多个请求重复使用同一个客户端实例，但当不再需要该客户端时，请在该客户端上调用 `close` 来释放资源。调用 close 方法后，将无法再使用客户端实例。

### 解析响应
<a name="examples-ec2-IMDS-use-pares"></a>

EC2 实例元数据可以以不同的格式输出。纯文本和 JSON 是最常用的格式。元数据客户端提供了使用这些格式的方法。

如以下示例所示，使用 `asString` 方法以 Java 字符串的形式获取数据。您还可以使用 `asList` 方法来分隔返回多行的纯文本响应。

```
Ec2MetadataClient client = Ec2MetadataClient.create();
Ec2MetadataResponse response = client.get("/latest/meta-data/");
String fullResponse = response.asString();
List<String> splits = response.asList();
```

如果响应采用 JSON 格式，请使用 `Ec2MetadataResponse#asDocument` 方法将 JSON 响应解析为 [Document](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/document/Document.html) 实例，如以下代码段所示。

```
Document fullResponse = response.asDocument();
```

如果元数据的格式不是 JSON，则会引发异常。如果成功解析了响应，则可以使用 [document API](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/document/package-summary.html) 来更详细地检查响应。请查阅实例[元数据类别表](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instancedata-data-categories.html)，了解哪些元数据类别提供了 JSON 格式的响应。

## 配置元数据客户端
<a name="examples-ec2-IMDS-config"></a>

### 重试
<a name="examples-ec2-IMDS-config-retries"></a>

您可以为元数据客户端配置重试机制。如果这样做，则客户端可以自动重试因意外原因而失败的请求。默认情况下，客户端对失败的请求重试三次，两次尝试之间的时间呈指数回退。

如果您的用例需要不同的重试机制，则可以使用其客户端生成器上的 `retryPolicy` 方法自定义客户端。例如，以下示例将同步客户端配置为共五次重试，每两次重试之间固定延迟两秒钟。

```
BackoffStrategy fixedBackoffStrategy = FixedDelayBackoffStrategy.create(Duration.ofSeconds(2));
Ec2MetadataClient client =
    Ec2MetadataClient.builder()
                     .retryPolicy(retryPolicyBuilder -> retryPolicyBuilder.numRetries(5)
                                                                           .backoffStrategy(fixedBackoffStrategy))
                     .build();
```

有几种[BackoffStrategies](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/retry/backoff/package-summary.html)可以与元数据客户端一起使用。

您也可以完全禁用重试机制，如以下代码段所示。

```
Ec2MetadataClient client =
    Ec2MetadataClient.builder()
                    .retryPolicy(Ec2MetadataRetryPolicy.none())
                    .build();
```

使用 `Ec2MetadataRetryPolicy#none()` 会禁用默认的重试策略，因此元数据客户端不尝试重试。

### IP 版本
<a name="examples-ec2-IMDS-config-ipversion"></a>

默认情况下，元数据客户端使用的 IPV4 终端节点`http://169.254.169.254`。要将客户端更改为使用 IPV6 版本，请使用构建器的`endpointMode`或`endpoint`方法。如果在生成器上同时调用这两个方法，则会出现异常。

以下示例显示了这两个 IPV6 选项。

```
Ec2MetadataClient client =
    Ec2MetadataClient.builder()
                     .endpointMode(EndpointMode.IPV6)
                     .build();
```

```
Ec2MetadataClient client =
    Ec2MetadataClient.builder()
                     .endpoint(URI.create("http://[fd00:ec2::254]"))
                     .build();
```

## 主要 功能
<a name="examples-ec2-IMDS-features"></a>

### 异步客户端
<a name="examples-ec2-IMDS-features-async"></a>

要使用非阻塞版本的客户端，请实例化 `Ec2MetadataAsyncClient` 类的实例。以下示例中的代码使用默认设置创建异步客户端，并使用 `get` 方法检索 `ami-id` 键的值。

```
Ec2MetadataAsyncClient asyncClient = Ec2MetadataAsyncClient.create();
CompletableFuture<Ec2MetadataResponse> response = asyncClient.get("/latest/meta-data/ami-id");
```

当响应返回时，`get` 方法返回的 `java.util.concurrent.CompletableFuture` 就完成了。以下示例将 `ami-id` 元数据打印到控制台。

```
response.thenAccept(metadata -> System.out.println(metadata.asString()));
```

### 可配置 HTTP 客户端
<a name="examples-ec2-IMDS-features-http"></a>

每个元数据客户端的生成器都有一种可用于提供自定义 HTTP 客户端的 `httpClient` 方法。

以下示例显示了自定义 `UrlConnectionHttpClient` 实例的代码。

```
SdkHttpClient httpClient =
    UrlConnectionHttpClient.builder()
                           .socketTimeout(Duration.ofMinutes(5))
                           .proxyConfiguration(proxy -> proxy.endpoint(URI.create("http://proxy.example.net:8888"))))
                           .build();
Ec2MetadataClient metaDataClient =
    Ec2MetadataClient.builder()
                     .httpClient(httpClient)
                     .build();
// Use the metaDataClient instance.
metaDataClient.close();   // Close the instance when no longer needed.
```

以下示例显示了带有异步元数据客户端的自定义 `NettyNioAsyncHttpClient` 实例的代码。

```
SdkAsyncHttpClient httpAsyncClient = 
    NettyNioAsyncHttpClient.builder()
                           .connectionTimeout(Duration.ofMinutes(5))
                           .maxConcurrency(100)
                           .build();
Ec2MetadataAsyncClient asyncMetaDataClient =
    Ec2MetadataAsyncClient.builder()
                          .httpClient(httpAsyncClient)
                          .build();
// Use the asyncMetaDataClient instance.
asyncMetaDataClient.close();   // Close the instance when no longer needed.
```

本指南中的 [在中配置 HTTP 客户端 AWS SDK for Java 2.x](http-configuration.md) 主题详细介绍了如何配置适用于 Java 的 SDK 中可用的 HTTP 客户端。

### 令牌缓存
<a name="examples-ec2-IMDS-features-token"></a>

由于客户端使用元数据 IMDSv2，因此所有请求都与会话关联。会话由带过期时间的令牌定义，元数据客户端会为您管理该令牌。每个元数据请求都会自动重复使用令牌，直到令牌过期。

默认情况下，令牌持续六小时（21600 秒）。除非您的特定用例需要高级配置，否则我们建议您保留默认 time-to-live值。

如果需要，可使用 `tokenTtl` 生成器方法配置持续时间。例如，以下代码段中的代码创建了一个会话持续时间为五分钟的客户端。

```
Ec2MetadataClient client =
    Ec2MetadataClient.builder()
                     .tokenTtl(Duration.ofMinutes(5))
                     .build();
```

如果您省略调用生成器上的 `tokenTtl` 方法，则改用默认持续时间 21,600。

# 与... 一起工作 IAM
<a name="examples-iam"></a>

本节提供使用 适用于 Java 的 AWS SDK 2.x 进行编程 AWS Identity and Access Management (IAM) 的示例。

 AWS Identity and Access Management (IAM) 使您能够安全地控制用户对 AWS 服务和资源的访问权限。使用 IAM，您可以创建和管理 AWS 用户和群组，并使用权限来允许和拒绝他们访问 AWS 资源。有关完整指南 IAM，请访问[IAM 用户指南](https://docs.aws.amazon.com//IAM/latest/UserGuide/introduction.html)。

以下示例仅包含演示每种方法所需的代码。[完整的示例代码可在上找到 GitHub](https://github.com/awsdocs/aws-doc-sdk-examples/tree/main/javav2)。您可以在那里下载单个源文件，也可以将存储库复制到本地以获得所有示例，然后构建并运行这些示例。

**Topics**
+ [管理 IAM 访问密钥](examples-iam-access-keys.md)
+ [管理 IAM 用户](examples-iam-users.md)
+ [创建 IAM policy](feature-iam-policy-builder.md)
+ [使用 IAM 策略](examples-iam-policies.md)
+ [使用 IAM 服务器证书](examples-iam-server-certificates.md)

# 管理 IAM 访问密钥
<a name="examples-iam-access-keys"></a>

## 创建访问密钥
<a name="create-an-access-key"></a>

要创建 IAM 访问密钥，请使用 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/CreateAccessKeyRequest.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/CreateAccessKeyRequest.html) 对象调用 `IamClient’s` `createAccessKey` 方法。

**注意**  
由于 ** 是一项全局服务，因此，您必须将区域设置为 **AWS\$1GLOBAL`IamClient` 才能使 IAM 调用生效。

 **导入**。

```
import software.amazon.awssdk.services.iam.model.CreateAccessKeyRequest;
import software.amazon.awssdk.services.iam.model.CreateAccessKeyResponse;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.iam.IamClient;
import software.amazon.awssdk.services.iam.model.IamException;
```

 **代码** 

```
    public static String createIAMAccessKey(IamClient iam,String user) {

        try {
            CreateAccessKeyRequest request = CreateAccessKeyRequest.builder()
                .userName(user).build();

            CreateAccessKeyResponse response = iam.createAccessKey(request);
           String keyId = response.accessKey().accessKeyId();
           return keyId;

        } catch (IamException e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
        return "";
    }
```

请参阅 GitHub 上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/f807d60010caf3d14fe4cd0801b842fb8e9511ca/javav2/example_code/iam/src/main/java/com/example/iam/CreateAccessKey.java)。

## 列出访问密钥
<a name="list-access-keys"></a>

要列出指定用户的访问密钥，请创建一个 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/ListAccessKeysRequest.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/ListAccessKeysRequest.html) 对象，其中包含要为其列出密钥的用户名，并将该对象传递给 `IamClient’s` `listAccessKeys` 方法。

**注意**  
如果您未向 `listAccessKeys` 提供用户名，则它将尝试列出与签署该请求的 AWS 账户相关联的访问密钥。

 **导入**。

```
import software.amazon.awssdk.services.iam.model.AccessKeyMetadata;
import software.amazon.awssdk.services.iam.model.IamException;
import software.amazon.awssdk.services.iam.model.ListAccessKeysRequest;
import software.amazon.awssdk.services.iam.model.ListAccessKeysResponse;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.iam.IamClient;
```

 **代码** 

```
    public static void listKeys( IamClient iam,String userName ){

        try {
            boolean done = false;
            String newMarker = null;

            while (!done) {
                ListAccessKeysResponse response;

            if(newMarker == null) {
                ListAccessKeysRequest request = ListAccessKeysRequest.builder()
                        .userName(userName).build();
                response = iam.listAccessKeys(request);
            } else {
                ListAccessKeysRequest request = ListAccessKeysRequest.builder()
                        .userName(userName)
                        .marker(newMarker).build();
                response = iam.listAccessKeys(request);
            }

            for (AccessKeyMetadata metadata :
                    response.accessKeyMetadata()) {
                System.out.format("Retrieved access key %s",
                        metadata.accessKeyId());
            }

            if (!response.isTruncated()) {
                done = true;
            } else {
                newMarker = response.marker();
            }
        }

        } catch (IamException e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
    }
```

`listAccessKeys` 的结果分页显示 (默认情况下，每个调用最多返回 100 个记录)。您可以调用返回的 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/ListAccessKeysResponse.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/ListAccessKeysResponse.html) 对象中的 `isTruncated`，以查看该查询返回的结果是否少于可用结果。如果是，则调用 `marker` 中的 `ListAccessKeysResponse` 并在创建新请求时使用它。在下次调用 `listAccessKeys` 时使用该新请求。

请参阅 GitHub 上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/f807d60010caf3d14fe4cd0801b842fb8e9511ca/javav2/example_code/iam/src/main/java/com/example/iam/ListAccessKeys.java)。

## 检索上次使用访问密钥的时间
<a name="retrieve-an-access-key-s-last-used-time"></a>

要获取上次使用访问密钥的时间，请使用访问密钥的 ID（可通过 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/GetAccessKeyLastUsedRequest.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/GetAccessKeyLastUsedRequest.html) 对象传入）调用 `IamClient’s` `getAccessKeyLastUsed` 方法。

然后，您可以使用返回的 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/GetAccessKeyLastUsedResponse.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/GetAccessKeyLastUsedResponse.html) 对象检索上次使用密钥的时间。

 **导入**。

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.iam.IamClient;
import software.amazon.awssdk.services.iam.model.GetAccessKeyLastUsedRequest;
import software.amazon.awssdk.services.iam.model.GetAccessKeyLastUsedResponse;
import software.amazon.awssdk.services.iam.model.IamException;
```

 **代码** 

```
    public static void getAccessKeyLastUsed(IamClient iam, String accessId ){

        try {
            GetAccessKeyLastUsedRequest request = GetAccessKeyLastUsedRequest.builder()
                    .accessKeyId(accessId).build();

            GetAccessKeyLastUsedResponse response = iam.getAccessKeyLastUsed(request);

            System.out.println("Access key was last used at: " +
                    response.accessKeyLastUsed().lastUsedDate());

        } catch (IamException e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
        System.out.println("Done");
    }
```

请参阅 GitHub 上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/f807d60010caf3d14fe4cd0801b842fb8e9511ca/javav2/example_code/iam/src/main/java/com/example/iam/AccessKeyLastUsed.java)。

## 激活或停用访问密钥
<a name="iam-access-keys-update"></a>

您可以激活或停用访问密钥，方式是创建 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/UpdateAccessKeyRequest.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/UpdateAccessKeyRequest.html) 对象，提供访问密钥 ID、用户名（可选）和所需 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/StatusType.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/StatusType.html)，然后将请求对象传递给 `IamClient’s` `updateAccessKey` 方法。

 **导入**。

```
import software.amazon.awssdk.services.iam.model.IamException;
import software.amazon.awssdk.services.iam.model.StatusType;
import software.amazon.awssdk.services.iam.model.UpdateAccessKeyRequest;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.iam.IamClient;
```

 **代码** 

```
       public static void updateKey(IamClient iam, String username, String accessId, String status ) {

          try {
              if (status.toLowerCase().equalsIgnoreCase("active")) {
                  statusType = StatusType.ACTIVE;
              } else if (status.toLowerCase().equalsIgnoreCase("inactive")) {
                  statusType = StatusType.INACTIVE;
              } else {
                  statusType = StatusType.UNKNOWN_TO_SDK_VERSION;
              }
              UpdateAccessKeyRequest request = UpdateAccessKeyRequest.builder()
                .accessKeyId(accessId)
                .userName(username)
                .status(statusType)
                .build();

              iam.updateAccessKey(request);

              System.out.printf(
                "Successfully updated the status of access key %s to" +
                        "status %s for user %s", accessId, status, username);

        } catch (IamException e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
    }
```

请参阅 GitHub 上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/f807d60010caf3d14fe4cd0801b842fb8e9511ca/javav2/example_code/iam/src/main/java/com/example/iam/UpdateAccessKey.java)。

## 删除访问密钥
<a name="delete-an-access-key"></a>

要永久删除访问密钥，请调用 `IamClient’s` `deleteKey` 方法，并为该方法提供包含访问密钥 ID 和用户名的 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/DeleteAccessKeyRequest.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/DeleteAccessKeyRequest.html)。

**注意**  
密钥在删除后无法再检索或使用。要临时停用密钥，使其可以稍后再次激活，请改用 [`updateAccessKey`](#iam-access-keys-update) 方法。

 **导入**。

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.iam.IamClient;
import software.amazon.awssdk.services.iam.model.DeleteAccessKeyRequest;
import software.amazon.awssdk.services.iam.model.IamException;
```

 **代码** 

```
    public static void deleteKey(IamClient iam ,String username, String accessKey ) {

        try {
            DeleteAccessKeyRequest request = DeleteAccessKeyRequest.builder()
                    .accessKeyId(accessKey)
                    .userName(username)
                    .build();

            iam.deleteAccessKey(request);
            System.out.println("Successfully deleted access key " + accessKey +
                " from user " + username);

        } catch (IamException e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
    }
```

请参阅 GitHub 上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/f807d60010caf3d14fe4cd0801b842fb8e9511ca/javav2/example_code/iam/src/main/java/com/example/iam/DeleteAccessKey.java)。

## 更多信息
<a name="more-information"></a>
+  《IAM API Reference》中的 [CreateAccessKey](https://docs.aws.amazon.com/IAM/latest/APIReference/API_CreateAccessKey.html)
+  《IAM API Reference》中的 [ListAccessKeys](https://docs.aws.amazon.com/IAM/latest/APIReference/API_ListAccessKeys.html)
+  《IAM API Reference》中的 [GetAccessKeyLastUsed](https://docs.aws.amazon.com/IAM/latest/APIReference/API_GetAccessKeyLastUsed.html)
+  《IAM API Reference》中的 [UpdateAccessKey](https://docs.aws.amazon.com/IAM/latest/APIReference/API_UpdateAccessKey.html)
+  《IAM API Reference》中的 [DeleteAccessKey](https://docs.aws.amazon.com/IAM/latest/APIReference/API_DeleteAccessKey.html)

# 管理 IAM 用户
<a name="examples-iam-users"></a>

## 创建用户
<a name="creating-a-user"></a>

使用包含用户名的 [CreateUserRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/CreateUserRequest.html) 对象向 IamClient 的 `createUser` 方法提供用户名，从而创建新 IAM 用户。

 **导入**。

```
import software.amazon.awssdk.core.waiters.WaiterResponse;
import software.amazon.awssdk.services.iam.model.CreateUserRequest;
import software.amazon.awssdk.services.iam.model.CreateUserResponse;
import software.amazon.awssdk.services.iam.model.IamException;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.iam.IamClient;
import software.amazon.awssdk.services.iam.waiters.IamWaiter;
import software.amazon.awssdk.services.iam.model.GetUserRequest;
import software.amazon.awssdk.services.iam.model.GetUserResponse;
```

 **代码** 

```
    public static String createIAMUser(IamClient iam, String username ) {

        try {
            // Create an IamWaiter object
            IamWaiter iamWaiter = iam.waiter();

            CreateUserRequest request = CreateUserRequest.builder()
                    .userName(username)
                    .build();

            CreateUserResponse response = iam.createUser(request);

            // Wait until the user is created
            GetUserRequest userRequest = GetUserRequest.builder()
                    .userName(response.user().userName())
                    .build();

            WaiterResponse<GetUserResponse> waitUntilUserExists = iamWaiter.waitUntilUserExists(userRequest);
            waitUntilUserExists.matched().response().ifPresent(System.out::println);
            return response.user().userName();

        } catch (IamException e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
       return "";
    }
```

请参阅 GitHub 上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/cf25559da654a7b74bec039c0ab9397dc5951dd4/javav2/example_code/iam/src/main/java/com/example/iam/CreateUser.java)。

## 列出用户
<a name="listing-users"></a>

要列出账户中的 IAM 用户，请创建新的 [ListUsersRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/ListUsersRequest.html) 并将其传递给 IamClient 的 `listUsers` 方法。您可以通过在返回的 `users`ListUsersResponse[ 对象上调用 ](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/ListUsersResponse.html) 来检索用户列表。

`listUsers` 返回的用户列表已分页。您可以通过调用响应对象的 `isTruncated` 方法查看更多可检索的结果。如果它返回 `true`，则调用响应对象的 `marker()` 方法。使用标记值创建新的请求对象。然后使用新请求再次调用 `listUsers` 方法。

 **导入**。

```
import software.amazon.awssdk.services.iam.model.IamException;
import software.amazon.awssdk.services.iam.model.ListUsersRequest;
import software.amazon.awssdk.services.iam.model.ListUsersResponse;
import software.amazon.awssdk.services.iam.model.User;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.iam.IamClient;
```

 **代码** 

```
    public static void listAllUsers(IamClient iam ) {

        try {

             boolean done = false;
             String newMarker = null;

             while(!done) {
                ListUsersResponse response;

                if (newMarker == null) {
                    ListUsersRequest request = ListUsersRequest.builder().build();
                    response = iam.listUsers(request);
                } else {
                    ListUsersRequest request = ListUsersRequest.builder()
                        .marker(newMarker).build();
                    response = iam.listUsers(request);
                }

                for(User user : response.users()) {
                 System.out.format("\n Retrieved user %s", user.userName());
                }

                if(!response.isTruncated()) {
                  done = true;
                } else {
                    newMarker = response.marker();
                }
            }
        } catch (IamException e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
    }
```

请参阅 GitHub 上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/cf25559da654a7b74bec039c0ab9397dc5951dd4/javav2/example_code/iam/src/main/java/com/example/iam/ListUsers.java)。

## 更新用户
<a name="updating-a-user"></a>

要更新用户，请调用 IamClient 对象的 `updateUser` 方法，该方法采用 [UpdateUserRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/UpdateUserRequest.html) 对象，您可以使用它更改用户的*名称* 或*路径*。

 **导入**。

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.iam.IamClient;
import software.amazon.awssdk.services.iam.model.IamException;
import software.amazon.awssdk.services.iam.model.UpdateUserRequest;
```

 **代码** 

```
    public static void updateIAMUser(IamClient iam, String curName,String newName ) {

        try {
            UpdateUserRequest request = UpdateUserRequest.builder()
                    .userName(curName)
                    .newUserName(newName)
                    .build();

            iam.updateUser(request);
            System.out.printf("Successfully updated user to username %s",
                newName);
        } catch (IamException e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
      }
```

请参阅 GitHub 上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/cf25559da654a7b74bec039c0ab9397dc5951dd4/javav2/example_code/iam/src/main/java/com/example/iam/UpdateUser.java)。

## 删除用户
<a name="deleting-a-user"></a>

要删除用户，请使用 [UpdateUserRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/UpdateUserRequest.html) 对象（其中设置了要删除的用户名）调用 IamClient 的 `deleteUser` 请求。

 **导入**。

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.iam.IamClient;
import software.amazon.awssdk.services.iam.model.DeleteUserRequest;
import software.amazon.awssdk.services.iam.model.IamException;
```

 **代码** 

```
    public static void deleteIAMUser(IamClient iam, String userName) {

        try {
            DeleteUserRequest request = DeleteUserRequest.builder()
                    .userName(userName)
                    .build();

            iam.deleteUser(request);
            System.out.println("Successfully deleted IAM user " + userName);
        } catch (IamException e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
    }
```

请参阅 GitHub 上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/cf25559da654a7b74bec039c0ab9397dc5951dd4/javav2/example_code/iam/src/main/java/com/example/iam/DeleteUser.java)。

## 更多信息
<a name="more-information"></a>
+  《IAM 用户指南》中的 [IAM 用户](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_users.html)
+  《IAM 用户指南》中的[管理 IAM 用户](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_users_manage.html)
+  《IAM API Reference》中的 [CreateUser](https://docs.aws.amazon.com/IAM/latest/APIReference/API_CreateUser.html)
+  《IAM API Reference》中的 [ListUsers](https://docs.aws.amazon.com/IAM/latest/APIReference/API_ListUsers.html)
+  《IAM API Reference》中的 [UpdateUser](https://docs.aws.amazon.com/IAM/latest/APIReference/API_UpdateUser.html)
+  《IAM API Reference》中的 [DeleteUser](https://docs.aws.amazon.com/IAM/latest/APIReference/API_DeleteUser.html)

# 使用创建 IAM 策略 AWS SDK for Java 2.x
<a name="feature-iam-policy-builder"></a>

[IAM 策略生成器 API](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/policybuilder/iam/package-summary.html) 是一个库，可用于在 Java 中构建 [IAM 策略](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html)并将其上传到 AWS Identity and Access Management (IAM)。

API 不是通过手动组装 JSON 字符串或读取文件来生成 IAM policy，而是提供了一种面向对象的客户端方法来生成 JSON 字符串。当您读取 JSON 格式的现有 IAM 策略时，API 会将其转换为[IamPolicy](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/policybuilder/iam/IamPolicy.html)实例进行处理。

IAM Policy 生成器 API 从 SDK 的 2.20.105 版本开始可用，因此请在 Maven 构建文件中使用该版本或更高版本。SDK 的最新版本号在 [Maven central 上列出](https://central.sonatype.com/artifact/software.amazon.awssdk/iam-policy-builder)。

以下代码段显示了 Maven `pom.xml` 文件的依赖项代码块示例。该代码块允许您在项目中使用 IAM policy 生成器 API。

```
<dependency>
    <groupId>software.amazon.awssdk</groupId>
    <artifactId>iam-policy-builder</artifactId>
    <version>2.27.21</version>
</dependency>
```

## 创建 `IamPolicy`
<a name="iam-policy-builder-create"></a>

本部分显示了如何使用 IAM policy 生成器 API 生成策略的几个示例。

以下每个示例都从 `[IamPolicy.Builder](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/policybuilder/iam/IamPolicy.Builder.html)` 开始，然后使用 `addStatement` 方法添加一条或多条语句。按照这种模式，[IamStatement.Builder](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/policybuilder/iam/IamStatement.Builder.html) 提供了向语句添加效果、操作、资源和条件的方法。

### 示例：创建基于时间的策略
<a name="iam-policy-builder-create-ex-time-based"></a>

以下示例创建了一个基于身份的策略，该策略允许在两个时间点之间执行 Amazon DynamoDB `GetItem` 操作。

```
    public String timeBasedPolicyExample() {
        IamPolicy policy = IamPolicy.builder()
                .addStatement(b -> b
                        .effect(IamEffect.ALLOW)
                        .addAction("dynamodb:GetItem")
                        .addResource(IamResource.ALL)
                        .addCondition(b1 -> b1
                                .operator(IamConditionOperator.DATE_GREATER_THAN)
                                .key("aws:CurrentTime")
                                .value("2020-04-01T00:00:00Z"))
                        .addCondition(b1 -> b1
                                .operator(IamConditionOperator.DATE_LESS_THAN)
                                .key("aws:CurrentTime")
                                .value("2020-06-30T23:59:59Z")))
                .build();

        // Use an IamPolicyWriter to write out the JSON string to a more readable format.
        return policy.toJson(IamPolicyWriter.builder()
                        .prettyPrint(true)
                        .build());
    }
```

#### JSON 输出
<a name="iam-builder-ex-json-date"></a>

以上示例中的最后一条语句返回以下 JSON 字符串。

有关此[示例](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_examples_aws-dates.html)的更多信息，请参阅《*AWS Identity and Access Management 用户指南*》。

------
#### [ JSON ]

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": {
        "Effect": "Allow",
        "Action": "dynamodb:GetItem",
        "Resource": "*",
        "Condition": {
            "DateGreaterThan": {
                "aws:CurrentTime": "2020-04-01T00:00:00Z"
            },
            "DateLessThan": {
                "aws:CurrentTime": "2020-06-30T23:59:59Z"
            }
        }
    }
}
```

------

### 示例：指定多个条件
<a name="iam-policy-builder-create-ex-multi-conditions"></a>

以下示例演示如何创建基于身份的策略，以允许访问特定 DynamoDB 属性。该政策包含两个条件。

```
    public String multipleConditionsExample() {
        IamPolicy policy = IamPolicy.builder()
                .addStatement(b -> b
                        .effect(IamEffect.ALLOW)
                        .addAction("dynamodb:GetItem")
                        .addAction("dynamodb:BatchGetItem")
                        .addAction("dynamodb:Query")
                        .addAction("dynamodb:PutItem")
                        .addAction("dynamodb:UpdateItem")
                        .addAction("dynamodb:DeleteItem")
                        .addAction("dynamodb:BatchWriteItem")
                        .addResource("arn:aws:dynamodb:*:*:table/table-name")
                        .addConditions(IamConditionOperator.STRING_EQUALS.addPrefix("ForAllValues:"),
                                "dynamodb:Attributes",
                                List.of("column-name1", "column-name2", "column-name3"))
                        .addCondition(b1 -> b1.operator(IamConditionOperator.STRING_EQUALS.addSuffix("IfExists"))
                                .key("dynamodb:Select")
                                .value("SPECIFIC_ATTRIBUTES")))
                .build();

        return policy.toJson(IamPolicyWriter.builder()
                .prettyPrint(true).build());
    }
```

#### JSON 输出
<a name="iam-builder-ex-json-multi-cond"></a>

以上示例中的最后一条语句返回以下 JSON 字符串。

有关此[示例](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_examples_dynamodb_attributes.html)的更多信息，请参阅《*AWS Identity and Access Management 用户指南*》。

------
#### [ JSON ]

****  

```
{
    "Version":"2012-10-17",		 	 	 
    "Statement": {
        "Effect": "Allow",
        "Action": [
            "dynamodb:GetItem",
            "dynamodb:BatchGetItem",
            "dynamodb:Query",
            "dynamodb:PutItem",
            "dynamodb:UpdateItem",
            "dynamodb:DeleteItem",
            "dynamodb:BatchWriteItem"
        ],
        "Resource": "arn:aws:dynamodb:*:*:table/table-name",
        "Condition": {
            "ForAllValues:StringEquals": {
                "dynamodb:Attributes": [
                    "column-name1",
                    "column-name2",
                    "column-name3"
                ]
            },
            "StringEqualsIfExists": {
                "dynamodb:Select": "SPECIFIC_ATTRIBUTES"
            }
        }
    }
}
```

------

### 示例：指定主体
<a name="iam-policy-builder-create-ex-principals"></a>

以下示例演示如何创建基于资源的策略，以拒绝除条件中指定的主体之外的所有主体访问某个桶。

```
    public String specifyPrincipalsExample() {
        IamPolicy policy = IamPolicy.builder()
                .addStatement(b -> b
                        .effect(IamEffect.DENY)
                        .addAction("s3:*")
                        .addPrincipal(IamPrincipal.ALL)
                        .addResource("arn:aws:s3:::BUCKETNAME/*")
                        .addResource("arn:aws:s3:::BUCKETNAME")
                        .addCondition(b1 -> b1
                                .operator(IamConditionOperator.ARN_NOT_EQUALS)
                                .key("aws:PrincipalArn")
                                .value("arn:aws:iam::444455556666:user/user-name")))
                .build();
        return policy.toJson(IamPolicyWriter.builder()
                .prettyPrint(true).build());
    }
```

#### JSON 输出
<a name="iam-policy-builder-create-json-ex-principals"></a>

以上示例中的最后一条语句返回以下 JSON 字符串。

有关此[示例](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_elements_principal.html#principal-anonymous)的更多信息，请参阅《*AWS Identity and Access Management 用户指南*》。

------
#### [ JSON ]

****  

```
{
  "Version":"2012-10-17",		 	 	 
  "Statement" : {
    "Effect" : "Deny",
    "Principal" : "*",
    "Action" : "s3:*",
    "Resource" : [ "arn:aws:s3:::BUCKETNAME/*", "arn:aws:s3:::BUCKETNAME" ],
    "Condition" : {
      "ArnNotEquals" : {
        "aws:PrincipalArn" : "arn:aws:iam::444455556666:user/user-name"
      }
    }
  }
}
```

------

### 示例：允许跨账户存取。
<a name="iam-policy-builder-create-ex-cross-account"></a>

以下示例说明如何允许其他人将对象上传 AWS 账户 到您的存储桶，同时保留所有者对上传对象的完全控制权。

```
    public String allowCrossAccountAccessExample() {
        IamPolicy policy = IamPolicy.builder()
                .addStatement(b -> b
                        .effect(IamEffect.ALLOW)
                        .addPrincipal(IamPrincipalType.AWS, "111122223333")
                        .addAction("s3:PutObject")
                        .addResource("arn:aws:s3:::amzn-s3-demo-bucket/*")
                        .addCondition(b1 -> b1
                                .operator(IamConditionOperator.STRING_EQUALS)
                                .key("s3:x-amz-acl")
                                .value("bucket-owner-full-control")))
                .build();
        return policy.toJson(IamPolicyWriter.builder()
                .prettyPrint(true).build());
    }
```

#### JSON 输出
<a name="iam-policy-builder-create-ex-json-cross-account"></a>

以上示例中的最后一条语句返回以下 JSON 字符串。

有关此[示例](https://docs.aws.amazon.com/AmazonS3/latest/userguide/example-bucket-policies.html#example-bucket-policies-acl-2)的更多信息，请参阅《*Amazon Simple Storage Service 用户指南*》。

------
#### [ JSON ]

****  

```
{
  "Version":"2012-10-17",		 	 	 
  "Statement" : {
    "Effect" : "Allow",
    "Principal" : {
      "AWS" : "111122223333"
    },
    "Action" : "s3:PutObject",
    "Resource" : "arn:aws:s3:::amzn-s3-demo-bucket/*",
    "Condition" : {
      "StringEquals" : {
        "s3:x-amz-acl" : "bucket-owner-full-control"
      }
    }
  }
}
```

------

## 将 `IamPolicy` 与 IAM 配合使用
<a name="iam-policy-builder-work-with-service"></a>

创建 `IamPolicy` 实例后，您可以通过 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/IamClient.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/IamClient.html) 来使用 IAM 服务。

以下示例构建了一个策略，允许 [IAM 身份](https://docs.aws.amazon.com/IAM/latest/UserGuide/id.html)向使用 `accountID` 参数指定的账户中的 DynamoDB 表写入项目。然后，策略会作为 JSON 字符串上传到 IAM。

```
    public String createAndUploadPolicyExample(IamClient iam, String accountID, String policyName) {
        // Build the policy.
        IamPolicy policy =
                IamPolicy.builder() // 'version' defaults to "2012-10-17".
                        .addStatement(IamStatement.builder()
                                .effect(IamEffect.ALLOW)
                                .addAction("dynamodb:PutItem")
                                .addResource("arn:aws:dynamodb:us-east-1:" + accountID + ":table/exampleTableName")
                                .build())
                        .build();
        // Upload the policy.
        iam.createPolicy(r -> r.policyName(policyName).policyDocument(policy.toJson()));
        return policy.toJson(IamPolicyWriter.builder().prettyPrint(true).build());
    }
```

下一个示例建立在前一个示例的基础上。该代码下载策略，并通过复制和修改声明将其用作新策略的基础。然后上传新策略。

```
    public String createNewBasedOnExistingPolicyExample(IamClient iam, String accountID, String policyName, String newPolicyName) {

        String policyArn = "arn:aws:iam::" + accountID + ":policy/" + policyName;
        GetPolicyResponse getPolicyResponse = iam.getPolicy(r -> r.policyArn(policyArn));

        String policyVersion = getPolicyResponse.policy().defaultVersionId();
        GetPolicyVersionResponse getPolicyVersionResponse =
                iam.getPolicyVersion(r -> r.policyArn(policyArn).versionId(policyVersion));

        // Create an IamPolicy instance from the JSON string returned from IAM.
        String decodedPolicy = URLDecoder.decode(getPolicyVersionResponse.policyVersion().document(), StandardCharsets.UTF_8);
        IamPolicy policy = IamPolicy.fromJson(decodedPolicy);

            /*
             All IamPolicy components are immutable, so use the copy method that creates a new instance that
             can be altered in the same method call.

             Add the ability to get an item from DynamoDB as an additional action.
            */
        IamStatement newStatement = policy.statements().get(0).copy(s -> s.addAction("dynamodb:GetItem"));

        // Create a new statement that replaces the original statement.
        IamPolicy newPolicy = policy.copy(p -> p.statements(Arrays.asList(newStatement)));

        // Upload the new policy. IAM now has both policies.
        iam.createPolicy(r -> r.policyName(newPolicyName)
                .policyDocument(newPolicy.toJson()));

        return newPolicy.toJson(IamPolicyWriter.builder().prettyPrint(true).build());
    }
```

### IamClient
<a name="iam-policy-builder-work-with-serivce-create-client"></a>

前面的示例使用了一个 `IamClient` 参数，它是使用如下所示的代码段创建的。

```
IamClient iam = IamClient.builder().region(Region.AWS_GLOBAL).build();
```

### JSON 格式的策略
<a name="iam-policy-builder-work-with-serivce-json"></a>

这些示例返回以下 JSON 字符串。

```
First example
{
  "Version": "2012-10-17",		 	 	 
  "Statement" : {
    "Effect" : "Allow",
    "Action" : "dynamodb:PutItem",
    "Resource" : "arn:aws:dynamodb:us-east-1:111122223333:table/exampleTableName"
  }
}

Second example
{
  "Version": "2012-10-17",		 	 	 
  "Statement" : {
    "Effect" : "Allow",
    "Action" : [ "dynamodb:PutItem", "dynamodb:GetItem" ],
    "Resource" : "arn:aws:dynamodb:us-east-1:111122223333:table/exampleTableName"
  }
}
```

# 使用 IAM 策略
<a name="examples-iam-policies"></a>

## 创建策略
<a name="create-a-policy"></a>

要创建新策略，请在 [CreatePolicyRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/CreatePolicyRequest.html) 中向 IamClient 的 `createPolicy` 方法提供策略名称和 JSON 格式的策略文档。

 **导入**。

```
import software.amazon.awssdk.core.waiters.WaiterResponse;
import software.amazon.awssdk.services.iam.model.CreatePolicyRequest;
import software.amazon.awssdk.services.iam.model.CreatePolicyResponse;
import software.amazon.awssdk.services.iam.model.GetPolicyRequest;
import software.amazon.awssdk.services.iam.model.GetPolicyResponse;
import software.amazon.awssdk.services.iam.model.IamException;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.iam.IamClient;
import software.amazon.awssdk.services.iam.waiters.IamWaiter;
```

 **代码** 

```
    public static String createIAMPolicy(IamClient iam, String policyName ) {

        try {
            // Create an IamWaiter object
            IamWaiter iamWaiter = iam.waiter();

            CreatePolicyRequest request = CreatePolicyRequest.builder()
                .policyName(policyName)
                .policyDocument(PolicyDocument).build();

            CreatePolicyResponse response = iam.createPolicy(request);

            // Wait until the policy is created
            GetPolicyRequest polRequest = GetPolicyRequest.builder()
                    .policyArn(response.policy().arn())
                    .build();

            WaiterResponse<GetPolicyResponse> waitUntilPolicyExists = iamWaiter.waitUntilPolicyExists(polRequest);
            waitUntilPolicyExists.matched().response().ifPresent(System.out::println);
            return response.policy().arn();

         } catch (IamException e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
        return "" ;
    }
```

请参阅 GitHub 上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/e41bacfd5e671e7ef1a9f73e972540dc6a434664/javav2/example_code/iam/src/main/java/com/example/iam/CreatePolicy.java)。

## 获取策略
<a name="get-a-policy"></a>

要检索现有策略，请调用 IamClient 的 `getPolicy` 方法，并在 [GetPolicyRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/GetPolicyRequest.html) 对象中提供策略的 ARN。

 **导入**。

```
import software.amazon.awssdk.services.iam.model.GetPolicyRequest;
import software.amazon.awssdk.services.iam.model.GetPolicyResponse;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.iam.IamClient;
import software.amazon.awssdk.services.iam.model.IamException;
```

 **代码** 

```
    public static void getIAMPolicy(IamClient iam, String policyArn) {

        try {
            GetPolicyRequest request = GetPolicyRequest.builder()
                .policyArn(policyArn).build();

            GetPolicyResponse response = iam.getPolicy(request);
            System.out.format("Successfully retrieved policy %s",
                response.policy().policyName());

        } catch (IamException e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
    }
```

请参阅 GitHub 上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/cf25559da654a7b74bec039c0ab9397dc5951dd4/javav2/example_code/iam/src/main/java/com/example/iam/GetPolicy.java)。

## 附加角色策略
<a name="attach-a-role-policy"></a>

您可以将策略附加到 IAM [角色](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles.html)，方式是调用 IamClient 的 `attachRolePolicy` 方法，并在 [AttachRolePolicyRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/AttachRolePolicyRequest.html) 中为其提供角色名称和策略 ARN。

 **导入**。

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.iam.IamClient;
import software.amazon.awssdk.services.iam.model.IamException;
import software.amazon.awssdk.services.iam.model.AttachRolePolicyRequest;
import software.amazon.awssdk.services.iam.model.AttachedPolicy;
import software.amazon.awssdk.services.iam.model.ListAttachedRolePoliciesRequest;
import software.amazon.awssdk.services.iam.model.ListAttachedRolePoliciesResponse;
import java.util.List;
```

 **代码** 

```
    public static void attachIAMRolePolicy(IamClient iam, String roleName, String policyArn ) {

        try {

             ListAttachedRolePoliciesRequest request = ListAttachedRolePoliciesRequest.builder()
                    .roleName(roleName)
                    .build();

            ListAttachedRolePoliciesResponse  response = iam.listAttachedRolePolicies(request);
            List<AttachedPolicy> attachedPolicies = response.attachedPolicies();

            // Ensure that the policy is not attached to this role
            String polArn = "";
            for (AttachedPolicy policy: attachedPolicies) {
                polArn = policy.policyArn();
                if (polArn.compareTo(policyArn)==0) {
                   System.out.println(roleName +
                            " policy is already attached to this role.");
                    return;
                }
          }

            AttachRolePolicyRequest attachRequest =
                AttachRolePolicyRequest.builder()
                        .roleName(roleName)
                        .policyArn(policyArn)
                        .build();

            iam.attachRolePolicy(attachRequest);

            System.out.println("Successfully attached policy " + policyArn +
                " to role " + roleName);

         } catch (IamException e) {
                System.err.println(e.awsErrorDetails().errorMessage());
                System.exit(1);
          }

     System.out.println("Done");
    }
```

请参阅 GitHub 上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/cf25559da654a7b74bec039c0ab9397dc5951dd4/javav2/example_code/iam/src/main/java/com/example/iam/AttachRolePolicy.java)。

## 列出附加的角色策略
<a name="list-attached-role-policies"></a>

通过调用 IamClient 的 `listAttachedRolePolicies` 方法列出角色中附加的策略。这需要 [ListAttachedRolePoliciesRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/ListAttachedRolePoliciesRequest.html) 对象，它包含要列出策略的角色名称。

在返回的 [ListAttachedRolePoliciesResponse](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/ListAttachedRolePoliciesResponse.html) 对象上调用 `getAttachedPolicies` 来获取所附加策略的列表。结果可能被截断；如果 `ListAttachedRolePoliciesResponse` 对象的 `isTruncated` 方法返回了 `true`，请调用 `ListAttachedRolePoliciesResponse` 对象的 `marker` 方法。使用返回的标记创建新请求并使用该请求再次调用 `listAttachedRolePolicies` 以获取下一批结果。

 **导入**。

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.iam.IamClient;
import software.amazon.awssdk.services.iam.model.IamException;
import software.amazon.awssdk.services.iam.model.AttachRolePolicyRequest;
import software.amazon.awssdk.services.iam.model.AttachedPolicy;
import software.amazon.awssdk.services.iam.model.ListAttachedRolePoliciesRequest;
import software.amazon.awssdk.services.iam.model.ListAttachedRolePoliciesResponse;
import java.util.List;
```

 **代码** 

```
    public static void attachIAMRolePolicy(IamClient iam, String roleName, String policyArn ) {

        try {

             ListAttachedRolePoliciesRequest request = ListAttachedRolePoliciesRequest.builder()
                    .roleName(roleName)
                    .build();

            ListAttachedRolePoliciesResponse  response = iam.listAttachedRolePolicies(request);
            List<AttachedPolicy> attachedPolicies = response.attachedPolicies();

            // Ensure that the policy is not attached to this role
            String polArn = "";
            for (AttachedPolicy policy: attachedPolicies) {
                polArn = policy.policyArn();
                if (polArn.compareTo(policyArn)==0) {
                   System.out.println(roleName +
                            " policy is already attached to this role.");
                    return;
                }
          }

            AttachRolePolicyRequest attachRequest =
                AttachRolePolicyRequest.builder()
                        .roleName(roleName)
                        .policyArn(policyArn)
                        .build();

            iam.attachRolePolicy(attachRequest);

            System.out.println("Successfully attached policy " + policyArn +
                " to role " + roleName);

         } catch (IamException e) {
                System.err.println(e.awsErrorDetails().errorMessage());
                System.exit(1);
          }

     System.out.println("Done");
    }
```

请参阅 GitHub 上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/cf25559da654a7b74bec039c0ab9397dc5951dd4/javav2/example_code/iam/src/main/java/com/example/iam/AttachRolePolicy.java)。

## 分离角色策略
<a name="detach-a-role-policy"></a>

要从角色分离策略，请调用 IamClient 的 `detachRolePolicy` 方法，并在 [DetachRolePolicyRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/DetachRolePolicyRequest.html) 中为其提供角色名称和策略 ARN。

 **导入**。

```
import software.amazon.awssdk.services.iam.model.DetachRolePolicyRequest;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.iam.IamClient;
import software.amazon.awssdk.services.iam.model.IamException;
```

 **代码** 

```
    public static void detachPolicy(IamClient iam, String roleName, String policyArn ) {

        try {
            DetachRolePolicyRequest request = DetachRolePolicyRequest.builder()
                    .roleName(roleName)
                    .policyArn(policyArn)
                    .build();

            iam.detachRolePolicy(request);
            System.out.println("Successfully detached policy " + policyArn +
                " from role " + roleName);

        } catch (IamException e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
    }
```

请参阅 GitHub 上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/cf25559da654a7b74bec039c0ab9397dc5951dd4/javav2/example_code/iam/src/main/java/com/example/iam/DetachRolePolicy.java)。

## 更多信息
<a name="more-information"></a>
+  《IAM 用户指南》中的 [IAM 策略概述](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies.html)。
+ 《IAM 用户指南》中的 [AWS IAM policy 参考](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies.html)。
+  《IAM API Reference》中的 [CreatePolicy](https://docs.aws.amazon.com/IAM/latest/APIReference/API_CreatePolicy.html)
+  《IAM API Reference》中的 [GetPolicy](https://docs.aws.amazon.com/IAM/latest/APIReference/API_GetPolicy.html)
+  《IAM API Reference》中的 [AttachRolePolicy](https://docs.aws.amazon.com/IAM/latest/APIReference/API_AttachRolePolicy.html)
+  《IAM API Reference》中的 [ListAttachedRolePolicies](https://docs.aws.amazon.com/IAM/latest/APIReference/API_ListAttachedRolePolicies.html)
+  《IAM API Reference》中的 [DetachRolePolicy](https://docs.aws.amazon.com/IAM/latest/APIReference/API_DetachRolePolicy.html)

# 使用 IAM 服务器证书
<a name="examples-iam-server-certificates"></a>

要启用与您的网站或应用程序的 HTTPS 连接 AWS，您需要 SSL/TLS *服务器证书*。您可以使用外部提供商提供的 AWS Certificate Manager 服务器证书，也可以使用从外部提供商处获得的服务器证书。

我们建议您使用 ACM 来预置、管理和部署服务器证书。借助此功能， ACM 您可以申请证书，将其部署到您的 AWS 资源中，然后让我们为您 ACM 处理证书续订。提供的证书 ACM 是免费的。有关的更多信息 ACM，请参阅《[AWS Certificate Manager 用户指南》](https://docs.aws.amazon.com/acm/latest/userguide/acm-overview.html)。

## 获取服务器证书
<a name="get-a-server-certificate"></a>

您可以通过调用 IamClient's `getServerCertificate` 方法来检索服务器证书，然后将其[GetServerCertificateRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/GetServerCertificateRequest.html)与证书名称一起传递。

 **导入** 

```
import software.amazon.awssdk.services.iam.model.GetServerCertificateRequest;
import software.amazon.awssdk.services.iam.model.GetServerCertificateResponse;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.iam.IamClient;
import software.amazon.awssdk.services.iam.model.IamException;
```

 **代码** 

```
    public static void getCertificate(IamClient iam,String certName ) {

        try {
            GetServerCertificateRequest request = GetServerCertificateRequest.builder()
                    .serverCertificateName(certName)
                    .build();

            GetServerCertificateResponse response = iam.getServerCertificate(request);
            System.out.format("Successfully retrieved certificate with body %s",
                response.serverCertificate().certificateBody());

         } catch (IamException e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
    }
```

请参阅上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/0b1785e42949ebf959eaa0f0da4dc2a48f92ea25/javav2/example_code/iam/src/main/java/com/example/iam/GetServerCertificate.java) GitHub。

## 列出服务器证书
<a name="list-server-certificates"></a>

要列出您的服务器证书，请使用调用 IamClient's `listServerCertificates` 方法[ListServerCertificatesRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/ListServerCertificatesRequest.html)。它返回 [ListServerCertificatesResponse](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/ListServerCertificatesResponse.html)。

调用返回`ListServerCertificateResponse`对象的`serverCertificateMetadataList`方法以获取可用于获取有关每个证书的信息的[ServerCertificateMetadata](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/ServerCertificateMetadata.html)对象列表。

如果 `ListServerCertificateResponse` 对象的 `isTruncated` 方法返回了 `true`，调用 `ListServerCertificatesResponse` 对象的 `marker` 方法并使用标记创建一个新请求，则结果可能被截断。使用该新请求重新调用 `listServerCertificates` 以获取下一批结果。

 **导入** 

```
import software.amazon.awssdk.services.iam.model.IamException;
import software.amazon.awssdk.services.iam.model.ListServerCertificatesRequest;
import software.amazon.awssdk.services.iam.model.ListServerCertificatesResponse;
import software.amazon.awssdk.services.iam.model.ServerCertificateMetadata;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.iam.IamClient;
```

 **代码** 

```
    public static void listCertificates(IamClient iam) {

        try {
            boolean done = false;
            String newMarker = null;

            while(!done) {
              ListServerCertificatesResponse response;

            if (newMarker == null) {
                ListServerCertificatesRequest request =
                        ListServerCertificatesRequest.builder().build();
                response = iam.listServerCertificates(request);
            } else {
                ListServerCertificatesRequest request =
                        ListServerCertificatesRequest.builder()
                                .marker(newMarker).build();
                response = iam.listServerCertificates(request);
            }

            for(ServerCertificateMetadata metadata :
                    response.serverCertificateMetadataList()) {
                System.out.printf("Retrieved server certificate %s",
                        metadata.serverCertificateName());
            }

            if(!response.isTruncated()) {
                done = true;
            } else {
                newMarker = response.marker();
            }
        }

        } catch (IamException e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
    }
```

请参阅上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/0b1785e42949ebf959eaa0f0da4dc2a48f92ea25/javav2/example_code/iam/src/main/java/com/example/iam/ListServerCertificates.java) GitHub。

## 更新服务器证书
<a name="update-a-server-certificate"></a>

您可以通过调用's `updateServerCertificate` 方法来更新服务器证书 IamClient的名称或路径。它需要一个包含服务器证书当前名称的[UpdateServerCertificateRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/UpdateServerCertificateRequest.html)对象集以及要使用的新名称或新路径。

 **导入** 

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.iam.IamClient;
import software.amazon.awssdk.services.iam.model.IamException;
import software.amazon.awssdk.services.iam.model.UpdateServerCertificateRequest;
import software.amazon.awssdk.services.iam.model.UpdateServerCertificateResponse;
```

 **代码** 

```
    public static void updateCertificate(IamClient iam, String curName, String newName) {

        try {
            UpdateServerCertificateRequest request =
                UpdateServerCertificateRequest.builder()
                        .serverCertificateName(curName)
                        .newServerCertificateName(newName)
                        .build();

            UpdateServerCertificateResponse response =
                iam.updateServerCertificate(request);


            System.out.printf("Successfully updated server certificate to name %s",
                newName);

        } catch (IamException e) {
             System.err.println(e.awsErrorDetails().errorMessage());
             System.exit(1);
        }
     }
```

请参阅上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/0b1785e42949ebf959eaa0f0da4dc2a48f92ea25/javav2/example_code/iam/src/main/java/com/example/iam/UpdateServerCertificate.java) GitHub。

## 删除服务器证书
<a name="delete-a-server-certificate"></a>

要删除服务器证书，请使用[DeleteServerCertificateRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/iam/model/DeleteServerCertificateRequest.html)包含证书名称 IamClient的's `deleteServerCertificate` 方法进行调用。

 **导入** 

```
import software.amazon.awssdk.services.iam.model.DeleteServerCertificateRequest;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.iam.IamClient;
import software.amazon.awssdk.services.iam.model.IamException;
```

 **代码** 

```
    public static void deleteCert(IamClient iam,String certName ) {

        try {
            DeleteServerCertificateRequest request =
                DeleteServerCertificateRequest.builder()
                        .serverCertificateName(certName)
                        .build();

            iam.deleteServerCertificate(request);
            System.out.println("Successfully deleted server certificate " +
                    certName);

        } catch (IamException e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
    }
```

请参阅上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/0b1785e42949ebf959eaa0f0da4dc2a48f92ea25/javav2/example_code/iam/src/main/java/com/example/iam/DeleteServerCertificate.java) GitHub。

## 更多信息
<a name="more-information"></a>
+  《 IAM 用户指南》中@@ [使用服务器证书](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_server-certs.html)
+  [GetServerCertificate](https://docs.aws.amazon.com/IAM/latest/APIReference/API_GetServerCertificate.html)在 IAM API 参考中
+  [ListServerCertificates](https://docs.aws.amazon.com/IAM/latest/APIReference/API_ListServerCertificates.html)在 IAM API 参考中
+  [UpdateServerCertificate](https://docs.aws.amazon.com/IAM/latest/APIReference/API_UpdateServerCertificate.html)在 IAM API 参考中
+  [DeleteServerCertificate](https://docs.aws.amazon.com/IAM/latest/APIReference/API_DeleteServerCertificate.html)在 IAM API 参考中
+  [AWS Certificate Manager 用户指南](https://docs.aws.amazon.com/acm/latest/userguide/) 

# 使用 Kinesis
<a name="examples-kinesis"></a>

本部分提供使用适用于 Java 的 AWS SDK 2.x 对 [Amazon Kinesis](https://docs.aws.amazon.com/kinesis/) 进行编程的示例。

有关 Kinesis 的更多信息，请参阅《[Amazon Kinesis 开发人员指南](https://docs.aws.amazon.com/streams/latest/dev/introduction.html)》。

以下示例仅包含演示每种方法所需的代码。[完整的示例代码在 GitHub 上提供](https://github.com/awsdocs/aws-doc-sdk-examples/tree/main/javav2)。您可以从中下载单个源文件，也可以将存储库复制到本地以获得所有示例，然后构建并运行它们。

**Topics**
+ [订阅 Amazon Kinesis Data Streams](examples-kinesis-stream.md)

# 订阅 Amazon Kinesis Data Streams
<a name="examples-kinesis-stream"></a>

以下示例演示如何使用 `subscribeToShard` 方法检索和处理 Amazon Kinesis Data Streams 中的数据。Kinesis Data Streams 现在采用增强的扇出功能和低延迟 HTTP/2 数据检索 API，同时让开发人员能够更易于在同一 Kinesis 数据流上运行多个低延迟、高性能的应用程序。

## 设置
<a name="set-up"></a>

首先，创建一个异步 Kinesis 客户端和一个 [SubscribeToShardRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/kinesis/model/SubscribeToShardRequest.html) 对象。这些对象用于以下每个示例中以订阅 Kinesis 事件。

 **导入**。

```
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.atomic.AtomicInteger;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import software.amazon.awssdk.core.async.SdkPublisher;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.kinesis.KinesisAsyncClient;
import software.amazon.awssdk.services.kinesis.model.ShardIteratorType;
import software.amazon.awssdk.services.kinesis.model.SubscribeToShardEvent;
import software.amazon.awssdk.services.kinesis.model.SubscribeToShardEventStream;
import software.amazon.awssdk.services.kinesis.model.SubscribeToShardRequest;
import software.amazon.awssdk.services.kinesis.model.SubscribeToShardResponse;
import software.amazon.awssdk.services.kinesis.model.SubscribeToShardResponseHandler;
```

 **代码** 

```
        Region region = Region.US_EAST_1;
        KinesisAsyncClient client = KinesisAsyncClient.builder()
        .region(region)
        .build();

        SubscribeToShardRequest request = SubscribeToShardRequest.builder()
                .consumerARN(CONSUMER_ARN)
                .shardId("arn:aws:kinesis:us-east-1:111122223333:stream/StockTradeStream")
                .startingPosition(s -> s.type(ShardIteratorType.LATEST)).build();
```

## 使用 Builder 接口
<a name="use-the-builder-interface"></a>

您可以使用 `builder` 方法来简化创建 [SubscribeToShardResponseHandler](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/kinesis/model/SubscribeToShardResponseHandler.html) 的过程。

使用生成器，您可以通过方法调用设置每个生命周期回调，而非实施完整的接口。

 **代码** 

```
    private static CompletableFuture<Void> responseHandlerBuilder(KinesisAsyncClient client, SubscribeToShardRequest request) {
        SubscribeToShardResponseHandler responseHandler = SubscribeToShardResponseHandler
                .builder()
                .onError(t -> System.err.println("Error during stream - " + t.getMessage()))
                .onComplete(() -> System.out.println("All records stream successfully"))
                // Must supply some type of subscriber
                .subscriber(e -> System.out.println("Received event - " + e))
                .build();
        return client.subscribeToShard(request, responseHandler);
    }
```

要对发布者进行更多控制，您可以使用 `publisherTransformer` 方法自定义发布者。

 **代码** 

```
    private static CompletableFuture<Void> responseHandlerBuilderPublisherTransformer(KinesisAsyncClient client, SubscribeToShardRequest request) {
        SubscribeToShardResponseHandler responseHandler = SubscribeToShardResponseHandler
                .builder()
                .onError(t -> System.err.println("Error during stream - " + t.getMessage()))
                .publisherTransformer(p -> p.filter(e -> e instanceof SubscribeToShardEvent).limit(100))
                .subscriber(e -> System.out.println("Received event - " + e))
                .build();
        return client.subscribeToShard(request, responseHandler);
    }
```

请参阅 GitHub 上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/ac748d8ef99cd17e297cb74fe13aa671e2679088/javav2/example_code/kinesis/src/main/java/com/example/kinesis/KinesisStreamEx.java)。

## 使用自定义响应处理程序
<a name="use-a-custom-response-handler"></a>

要完全控制订阅用户和发布者，请实施 `SubscribeToShardResponseHandler` 接口。

在本示例中，您实施 `onEventStream` 方法，此方法允许您对发布者具有完全访问权限。此示例演示如何将发布者转换为事件记录以供订阅者打印。

 **代码** 

```
    private static CompletableFuture<Void> responseHandlerBuilderClassic(KinesisAsyncClient client, SubscribeToShardRequest request) {
        SubscribeToShardResponseHandler responseHandler = new SubscribeToShardResponseHandler() {

            @Override
            public void responseReceived(SubscribeToShardResponse response) {
                System.out.println("Receieved initial response");
            }

            @Override
            public void onEventStream(SdkPublisher<SubscribeToShardEventStream> publisher) {
                publisher
                        // Filter to only SubscribeToShardEvents
                        .filter(SubscribeToShardEvent.class)
                        // Flat map into a publisher of just records
                        .flatMapIterable(SubscribeToShardEvent::records)
                        // Limit to 1000 total records
                        .limit(1000)
                        // Batch records into lists of 25
                        .buffer(25)
                        // Print out each record batch
                        .subscribe(batch -> System.out.println("Record Batch - " + batch));
            }

            @Override
            public void complete() {
                System.out.println("All records stream successfully");
            }

            @Override
            public void exceptionOccurred(Throwable throwable) {
                System.err.println("Error during stream - " + throwable.getMessage());
            }
        };
        return client.subscribeToShard(request, responseHandler);
    }
```

请参阅 GitHub 上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/ac748d8ef99cd17e297cb74fe13aa671e2679088/javav2/example_code/kinesis/src/main/java/com/example/kinesis/KinesisStreamEx.java)。

## 使用 Visitor 接口
<a name="use-the-visitor-interface"></a>

您可以使用 [Visitor](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/kinesis/model/SubscribeToShardResponseHandler.Visitor.html) 对象订阅您有兴趣观看的特定事件。

 **代码** 

```
    private static CompletableFuture<Void> responseHandlerBuilderVisitorBuilder(KinesisAsyncClient client, SubscribeToShardRequest request) {
        SubscribeToShardResponseHandler.Visitor visitor = SubscribeToShardResponseHandler.Visitor
                .builder()
                .onSubscribeToShardEvent(e -> System.out.println("Received subscribe to shard event " + e))
                .build();
        SubscribeToShardResponseHandler responseHandler = SubscribeToShardResponseHandler
                .builder()
                .onError(t -> System.err.println("Error during stream - " + t.getMessage()))
                .subscriber(visitor)
                .build();
        return client.subscribeToShard(request, responseHandler);
    }
```

请参阅 GitHub 上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/ac748d8ef99cd17e297cb74fe13aa671e2679088/javav2/example_code/kinesis/src/main/java/com/example/kinesis/KinesisStreamEx.java)。

## 使用自定义订阅者
<a name="use-a-custom-subscriber"></a>

您也可以实施您自己的自定义订阅者以订阅流。

此代码段显示了一个示例订阅者。

 **代码** 

```
    private static class MySubscriber implements Subscriber<SubscribeToShardEventStream> {

        private Subscription subscription;
        private AtomicInteger eventCount = new AtomicInteger(0);

        @Override
        public void onSubscribe(Subscription subscription) {
            this.subscription = subscription;
            this.subscription.request(1);
        }

        @Override
        public void onNext(SubscribeToShardEventStream shardSubscriptionEventStream) {
            System.out.println("Received event " + shardSubscriptionEventStream);
            if (eventCount.incrementAndGet() >= 100) {
                // You can cancel the subscription at any time if you wish to stop receiving events.
                subscription.cancel();
            }
            subscription.request(1);
        }

        @Override
        public void onError(Throwable throwable) {
            System.err.println("Error occurred while stream - " + throwable.getMessage());
        }

        @Override
        public void onComplete() {
            System.out.println("Finished streaming all events");
        }
    }
```

您可以将自定义订阅者传递给 `subscribe` 方法，如以下代码片段所示。

 **代码** 

```
    private static CompletableFuture<Void> responseHandlerBuilderSubscriber(KinesisAsyncClient client, SubscribeToShardRequest request) {
        SubscribeToShardResponseHandler responseHandler = SubscribeToShardResponseHandler
                .builder()
                .onError(t -> System.err.println("Error during stream - " + t.getMessage()))
                .subscriber(MySubscriber::new)
                .build();
        return client.subscribeToShard(request, responseHandler);
    }
```

请参阅 GitHub 上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/ac748d8ef99cd17e297cb74fe13aa671e2679088/javav2/example_code/kinesis/src/main/java/com/example/kinesis/KinesisStreamEx.java)。

## 将数据记录写入 Kinesis 数据流
<a name="write-data-records-into-a-kinesis-data-stream"></a>

您可以使用 [KinesisClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/kinesis/KinesisClient.html) 对象通过 `putRecords` 方法将数据记录写入 Kinesis 数据流。要成功调用此方法，请创建一个 [PutRecordsRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/kinesis/model/PutRecordsRequest.html) 对象。将数据流的名称传递给 `streamName` 方法。此外，您必须使用 `putRecords` 方法传递数据（如下面的代码示例所示）。

 **导入**。

```
import software.amazon.awssdk.core.SdkBytes;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.kinesis.KinesisClient;
import software.amazon.awssdk.services.kinesis.model.PutRecordRequest;
import software.amazon.awssdk.services.kinesis.model.KinesisException;
import software.amazon.awssdk.services.kinesis.model.DescribeStreamRequest;
import software.amazon.awssdk.services.kinesis.model.DescribeStreamResponse;
```

在以下 Java 代码示例中，请注意 **StockTrade** 对象用作写入 Kinesis 数据流的数据。在运行此示例之前，请确保已创建数据流。

 **代码** 

```
import software.amazon.awssdk.core.SdkBytes;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.kinesis.KinesisClient;
import software.amazon.awssdk.services.kinesis.model.PutRecordRequest;
import software.amazon.awssdk.services.kinesis.model.KinesisException;
import software.amazon.awssdk.services.kinesis.model.DescribeStreamRequest;
import software.amazon.awssdk.services.kinesis.model.DescribeStreamResponse;

/**
 * Before running this Java V2 code example, set up your development
 * environment, including your credentials.
 *
 * For more information, see the following documentation topic:
 *
 * https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/get-started.html
 */
public class StockTradesWriter {
    public static void main(String[] args) {
        final String usage = """

                Usage:
                    <streamName>

                Where:
                    streamName - The Amazon Kinesis data stream to which records are written (for example, StockTradeStream)
                """;

        if (args.length != 1) {
            System.out.println(usage);
            System.exit(1);
        }

        String streamName = args[0];
        Region region = Region.US_EAST_1;
        KinesisClient kinesisClient = KinesisClient.builder()
                .region(region)
                .build();

        // Ensure that the Kinesis Stream is valid.
        validateStream(kinesisClient, streamName);
        setStockData(kinesisClient, streamName);
        kinesisClient.close();
    }

    public static void setStockData(KinesisClient kinesisClient, String streamName) {
        try {
            // Repeatedly send stock trades with a 100 milliseconds wait in between.
            StockTradeGenerator stockTradeGenerator = new StockTradeGenerator();

            // Put in 50 Records for this example.
            int index = 50;
            for (int x = 0; x < index; x++) {
                StockTrade trade = stockTradeGenerator.getRandomTrade();
                sendStockTrade(trade, kinesisClient, streamName);
                Thread.sleep(100);
            }

        } catch (KinesisException | InterruptedException e) {
            System.err.println(e.getMessage());
            System.exit(1);
        }
        System.out.println("Done");
    }

    private static void sendStockTrade(StockTrade trade, KinesisClient kinesisClient,
            String streamName) {
        byte[] bytes = trade.toJsonAsBytes();

        // The bytes could be null if there is an issue with the JSON serialization by
        // the Jackson JSON library.
        if (bytes == null) {
            System.out.println("Could not get JSON bytes for stock trade");
            return;
        }

        System.out.println("Putting trade: " + trade);
        PutRecordRequest request = PutRecordRequest.builder()
                .partitionKey(trade.getTickerSymbol()) // We use the ticker symbol as the partition key, explained in
                                                       // the Supplemental Information section below.
                .streamName(streamName)
                .data(SdkBytes.fromByteArray(bytes))
                .build();

        try {
            kinesisClient.putRecord(request);
        } catch (KinesisException e) {
            System.err.println(e.getMessage());
        }
    }

    private static void validateStream(KinesisClient kinesisClient, String streamName) {
        try {
            DescribeStreamRequest describeStreamRequest = DescribeStreamRequest.builder()
                    .streamName(streamName)
                    .build();

            DescribeStreamResponse describeStreamResponse = kinesisClient.describeStream(describeStreamRequest);

            if (!describeStreamResponse.streamDescription().streamStatus().toString().equals("ACTIVE")) {
                System.err.println("Stream " + streamName + " is not active. Please wait a few moments and try again.");
                System.exit(1);
            }

        } catch (KinesisException e) {
            System.err.println("Error found while describing the stream " + streamName);
            System.err.println(e);
            System.exit(1);
        }
    }
}
```

请参阅 GitHub 上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/master/javav2/example_code/kinesis/src/main/java/com/example/kinesis/StockTradesWriter.java)。

## 使用第三方库
<a name="use-a-third-party-library"></a>

您可以使用其他第三方库，而不是实现自定义订阅者。本示例演示了如何使用 RxJava 实现，但您可以使用实现反应式流接口的任何库。有关该库的更多信息，请参阅 [Github 上的 RxJava 维基页面](https://github.com/ReactiveX/RxJava/wiki)。

要使用该库，请将其作为依赖项添加。如果您使用 Maven，示例将显示要使用的 POM 代码段。

 **POM 条目** 

```
<dependency>
 <groupId>io.reactivex.rxjava2</groupId>
 <artifactId>rxjava</artifactId>
 <version>2.2.21</version>
</dependency>
```

 **导入**。

```
import java.net.URI;
import java.util.concurrent.CompletableFuture;

import io.reactivex.Flowable;
import software.amazon.awssdk.auth.credentials.ProfileCredentialsProvider;
import software.amazon.awssdk.core.async.SdkPublisher;
import software.amazon.awssdk.http.Protocol;
import software.amazon.awssdk.http.SdkHttpConfigurationOption;
import software.amazon.awssdk.http.nio.netty.NettyNioAsyncHttpClient;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.kinesis.KinesisAsyncClient;
import software.amazon.awssdk.services.kinesis.model.ShardIteratorType;
import software.amazon.awssdk.services.kinesis.model.StartingPosition;
import software.amazon.awssdk.services.kinesis.model.SubscribeToShardEvent;
import software.amazon.awssdk.services.kinesis.model.SubscribeToShardRequest;
import software.amazon.awssdk.services.kinesis.model.SubscribeToShardResponseHandler;
import software.amazon.awssdk.utils.AttributeMap;
```

此示例在 `onEventStream` 生命周期方法中使用 RxJava。这样，您就对发布者具有完全访问权限，这可用于创建 Rx Flowable。

 **代码** 

```
        SubscribeToShardResponseHandler responseHandler = SubscribeToShardResponseHandler
            .builder()
            .onError(t -> System.err.println("Error during stream - " + t.getMessage()))
            .onEventStream(p -> Flowable.fromPublisher(p)
                                        .ofType(SubscribeToShardEvent.class)
                                        .flatMapIterable(SubscribeToShardEvent::records)
                                        .limit(1000)
                                        .buffer(25)
                                        .subscribe(e -> System.out.println("Record batch = " + e)))
            .build();
```

您也可以使用带有 `publisherTransformer` 发布者的 `Flowable` 方法。您必须使 `Flowable` 发布者适应 *SdkPublisher*，如以下示例所示。

 **代码** 

```
        SubscribeToShardResponseHandler responseHandler = SubscribeToShardResponseHandler
            .builder()
            .onError(t -> System.err.println("Error during stream - " + t.getMessage()))
            .publisherTransformer(p -> SdkPublisher.adapt(Flowable.fromPublisher(p).limit(100)))
            .build();
```

请参阅 GitHub 上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/main/javav2/example_code/kinesis/src/main/java/com/example/kinesis/KinesisStreamRxJavaEx.java)。

## 更多信息
<a name="more-information"></a>
+  《Amazon Kinesis API Reference》中的 [SubscribeToShardEvent](https://docs.aws.amazon.com/kinesis/latest/APIReference/API_SubscribeToShardEvent.html)
+  《Amazon Kinesis API Reference》中的 [SubscribeToShard](https://docs.aws.amazon.com/kinesis/latest/APIReference/API_SubscribeToShard.html)

# 调用、列出和删除 AWS Lambda 函数
<a name="examples-lambda"></a>

本节提供了使用 适用于 Java 的 AWS SDK 2.x 使用 Lambda 服务客户端进行编程的示例。

**Topics**
+ [调用 Lambda 函数](#invoke-function)
+ [列出 Lambda 函数](#list-function)
+ [删除 Lambda 函数](#delete-function)

## 调用 Lambda 函数
<a name="invoke-function"></a>

您可以通过创建[https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/lambda/LambdaClient.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/lambda/LambdaClient.html)对象并调用其`invoke`方法来调用 Lambda 函数。创建一个[https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/lambda/model/InvokeRequest.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/lambda/model/InvokeRequest.html)对象以指定其他信息，例如要传递给函数的函数名称和有效负载。 Lambda 函数名称显示为 *arn: aws: lambda: us-east-1:123456789012:* function:。HelloFunction可以通过查看 AWS 管理控制台中的函数来检索值。

要将有效载荷数据传递给函数，请创建包含信息的 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/SdkBytes.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/SdkBytes.html) 对象。例如，在以下代码示例中，请注意传递给 Lambda 函数的 JSON 数据。

 **导入** 

```
import software.amazon.awssdk.services.lambda.LambdaClient;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.lambda.model.InvokeRequest;
import software.amazon.awssdk.core.SdkBytes;
import software.amazon.awssdk.services.lambda.model.InvokeResponse;
import software.amazon.awssdk.services.lambda.model.LambdaException;
```

 **代码** 

以下代码示例演示了如何调用 Lambda 函数。

```
    public static void invokeFunction(LambdaClient awsLambda, String functionName) {

         InvokeResponse res = null ;
        try {
            //Need a SdkBytes instance for the payload
            String json = "{\"Hello \":\"Paris\"}";
            SdkBytes payload = SdkBytes.fromUtf8String(json) ;

            //Setup an InvokeRequest
            InvokeRequest request = InvokeRequest.builder()
                    .functionName(functionName)
                    .payload(payload)
                    .build();

            res = awsLambda.invoke(request);
            String value = res.payload().asUtf8String() ;
            System.out.println(value);

        } catch(LambdaException e) {
            System.err.println(e.getMessage());
            System.exit(1);
        }
    }
```

请参阅上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/f807d60010caf3d14fe4cd0801b842fb8e9511ca/javav2/example_code/lambda/src/main/java/com/example/lambda/LambdaInvoke.java) GitHub。

## 列出 Lambda 函数
<a name="list-function"></a>

构建一个 `[Lambda Client](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/lambda/LambdaClient.html)` 对象并调用其 `listFunctions` 方法。此方法返回一个 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/lambda/model/ListFunctionsResponse.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/lambda/model/ListFunctionsResponse.html) 对象。您可以调用此对象的 `functions` 方法以返回 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/lambda/model/FunctionConfiguration.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/lambda/model/FunctionConfiguration.html) 对象的列表。可以遍历该列表来检索有关函数的信息。例如，以下 Java 代码示例说明如何获取每个函数名称。

 **导入** 

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.lambda.LambdaClient;
import software.amazon.awssdk.services.lambda.model.LambdaException;
import software.amazon.awssdk.services.lambda.model.ListFunctionsResponse;
import software.amazon.awssdk.services.lambda.model.FunctionConfiguration;
import java.util.List;
```

 **代码** 

以下 Java 代码示例演示如何检索 函数名称的列表。

```
    public static void listFunctions(LambdaClient awsLambda) {

        try {
            ListFunctionsResponse functionResult = awsLambda.listFunctions();
            List<FunctionConfiguration> list = functionResult.functions();

            for (FunctionConfiguration config: list) {
                System.out.println("The function name is "+config.functionName());
            }

        } catch(LambdaException e) {
            System.err.println(e.getMessage());
            System.exit(1);
        }
    }
```

请参阅上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/f807d60010caf3d14fe4cd0801b842fb8e9511ca/javav2/example_code/lambda/src/main/java/com/example/lambda/ListLambdaFunctions.java) GitHub。

## 删除 Lambda 函数
<a name="delete-function"></a>

构建一个 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/lambda/LambdaClient.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/lambda/LambdaClient.html) 对象并调用其 `deleteFunction` 方法。创建一个 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/lambda/model/DeleteFunctionRequest.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/lambda/model/DeleteFunctionRequest.html) 对象并将其传递给 `deleteFunction` 方法。此对象包含要删除的函数的名称等信息。函数名称显示为 *arn: aws: lambda: us-east-1:123456789012:* function:。HelloFunction可以通过查看 AWS 管理控制台中的函数来检索值。

 **导入** 

```
import software.amazon.awssdk.services.lambda.LambdaClient;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.lambda.model.DeleteFunctionRequest;
import software.amazon.awssdk.services.lambda.model.LambdaException;
```

 **代码** 

以下 Java 代码演示了如何删除 Lambda 函数。

```
    public static void deleteLambdaFunction(LambdaClient awsLambda, String functionName ) {
        try {
            DeleteFunctionRequest request = DeleteFunctionRequest.builder()
                    .functionName(functionName)
                    .build();

            awsLambda.deleteFunction(request);
            System.out.println("The "+functionName +" function was deleted");

        } catch(LambdaException e) {
            System.err.println(e.getMessage());
            System.exit(1);
        }
    }
```

请参阅上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/f807d60010caf3d14fe4cd0801b842fb8e9511ca/javav2/example_code/lambda/src/main/java/com/example/lambda/DeleteFunction.java) GitHub。

# 使用 Amazon S3
<a name="examples-s3"></a>

本节提供有关使用 AWS SDK for Java 2.x来处理 Amazon S3 的背景信息。本节对本指南的*代码示例*部分中介绍的 [Amazon S3 Java v2 示例](java_s3_code_examples.md)作出了补充。

## 中的 S3 客户端 AWS SDK for Java 2.x
<a name="s3-clients"></a>

 AWS SDK for Java 2.x 提供了不同类型的 S3 客户端。下表显示了不同客户端的差异，可以帮助您确定哪种客户端更适合您的使用案例。


**Amazon S3 客户端的不同类型**  

| S3 客户端 | 简短描述 | 何时使用 | 限制/缺点 | 
| --- | --- | --- | --- | 
|  **AWS 基于CRT的S3客户端** 接口：[S3 AsyncClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3AsyncClient.html) 生成器：[S3 CrtAsyncClientBuilder](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3AsyncClient.html)  |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_cn/sdk-for-java/latest/developer-guide/examples-s3.html) 请参阅[使用高性能 S3 客户端： AWS 基于 CRT 的 S3 客户端](crt-based-s3-client.md)。  |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_cn/sdk-for-java/latest/developer-guide/examples-s3.html)  |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_cn/sdk-for-java/latest/developer-guide/examples-s3.html)  | 
|  **基于 Java 的 S3 异步客户端，*启用了*分段功能** 接口：[S3 AsyncClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3AsyncClient.html) 生成器：[S3 AsyncClientBuilder](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3AsyncClientBuilder.html)  |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_cn/sdk-for-java/latest/developer-guide/examples-s3.html) 请参阅[将基于 Java 的 S3 异步客户端配置为使用并行传输](s3-async-client-multipart.md)。  |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_cn/sdk-for-java/latest/developer-guide/examples-s3.html)  | 性能不如基于 AWS CRT 的 S3 客户端。 | 
|  **基于 Java 的 S3 异步客户端，*未启用*分段功能** 接口：[S3 AsyncClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3AsyncClient.html) 生成器：[S3 AsyncClientBuilder](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3AsyncClientBuilder.html) |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_cn/sdk-for-java/latest/developer-guide/examples-s3.html)  |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_cn/sdk-for-java/latest/developer-guide/examples-s3.html)  |  没有性能优化。  | 
|  **基于 Java 的 S3 同步客户端** 接口：[S3Client](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3Client.html) 生成器：[S3 ClientBuilder](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3ClientBuilder.html) |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_cn/sdk-for-java/latest/developer-guide/examples-s3.html)  |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_cn/sdk-for-java/latest/developer-guide/examples-s3.html)  |  没有性能优化。  | 

**注意**  
从版本 2.18.x 及更高版本开始，在包含终端 AWS SDK for Java 2.x 节点覆盖时使用[虚拟托管式寻址](https://docs.aws.amazon.com/AmazonS3/latest/userguide/VirtualHosting.html#virtual-hosted-style-access)。这适用于只要桶名称是有效 DNS 标签的所有情况。  
在客户端生成器中使用 `true` 调用 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3BaseClientBuilder.html#forcePathStyle(java.lang.Boolean](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3BaseClientBuilder.html#forcePathStyle(java.lang.Boolean) 方法，可强制客户端对桶使用路径式寻址。  
以下示例显示了配置了端点覆盖并使用路径式寻址的服务客户端。  

```
S3Client client = S3Client.builder()
                          .region(Region.US_WEST_2)
                          .endpointOverride(URI.create("https://s3.us-west-2.amazonaws.com"))
                          .forcePathStyle(true)
                          .build();
```

**Topics**
+ [SDK 中的 S3 客户端](#s3-clients)
+ [将流上传到 S3](best-practices-s3-uploads.md)
+ [预先签名 URLs](examples-s3-presign.md)
+ [跨区域访问](s3-cross-region.md)
+ [使用校验和实现数据完整性保护](s3-checksums.md)
+ [使用高性能 S3 客户端](crt-based-s3-client.md)
+ [配置并行传输支持](s3-async-client-multipart.md)
+ [传输文件和目录](transfer-manager.md)
+ [S3 事件通知](examples-s3-event-notifications.md)

# 使用将直播上传到 Amazon S3 AWS SDK for Java 2.x
<a name="best-practices-s3-uploads"></a>

当您通过 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3Client.html#putObject(software.amazon.awssdk.services.s3.model.PutObjectRequest,software.amazon.awssdk.core.sync.RequestBody)](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3Client.html#putObject(software.amazon.awssdk.services.s3.model.PutObjectRequest,software.amazon.awssdk.core.sync.RequestBody)) 或 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3Client.html#uploadPart(software.amazon.awssdk.services.s3.model.UploadPartRequest,software.amazon.awssdk.core.sync.RequestBody)](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3Client.html#uploadPart(software.amazon.awssdk.services.s3.model.UploadPartRequest,software.amazon.awssdk.core.sync.RequestBody)) 方法，使用流将内容上传到 S3 时，可以使用 `RequestBody` 工厂类作为同步 API 来提供流。对于异步 API，`AsyncRequestBody` 是等效的工厂类。

## 哪些方法可上传流？
<a name="s3-stream-upload-methods"></a>

对于同步 API，您可以使用 `RequestBody` 的以下工厂方法来提供流：
+ `[fromInputStream](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/sync/RequestBody.html#fromInputStream(java.io.InputStream,long))(InputStream inputStream, long contentLength)`

  `[fromContentProvider](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/sync/RequestBody.html#fromContentProvider(software.amazon.awssdk.http.ContentStreamProvider,long,java.lang.String))(ContentStreamProvider provider, long contentLength, String mimeType)`
  + `ContentStreamProvider` 有 `fromInputStream(InputStream inputStream)` 工厂方法
+ `[fromContentProvider](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/sync/RequestBody.html#fromContentProvider(software.amazon.awssdk.http.ContentStreamProvider,java.lang.String))(ContentStreamProvider provider, String mimeType)`

对于异步 API，您可以使用 `AsyncRequestBody` 的以下工厂方法：
+ `[fromInputStream](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/async/AsyncRequestBody.html#fromInputStream(java.io.InputStream,java.lang.Long,java.util.concurrent.ExecutorService))(InputStream inputStream, Long contentLength, ExecutorService executor)` 
+ `[fromInputStream](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/async/AsyncRequestBody.html#fromInputStream(software.amazon.awssdk.core.async.AsyncRequestBodyFromInputStreamConfiguration))(AsyncRequestBodyFromInputStreamConfiguration configuration)`
  + 你可以使用 AsyncRequestBodyFromInputStreamConfiguration .Builder 来提供直播
+ `[fromInputStream](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/async/AsyncRequestBody.html#fromInputStream(java.util.function.Consumer))(Consumer<AsyncRequestBodyFromInputStreamConfiguration.Builder> configuration)`
+ `[forBlockingInputStream](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/async/AsyncRequestBody.html#forBlockingInputStream(java.lang.Long))(Long contentLength)`
  + 生成的 `[BlockingInputStreamAsyncRequestBody](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/async/BlockingInputStreamAsyncRequestBody.html)` 包含 `writeInputStream(InputStream inputStream)` 方法，您可以使用该方法来提供流

## 执行上传
<a name="s3-upload-stream-perform"></a>

### 如果您知道流的长度
<a name="s3-stream-upload-supply-content-length"></a>

从前面所示方法的签名中可以看出，大多数方法都接受内容长度参数。

如果您知道以字节为单位的内容长度，请提供确切值：

```
// Always provide the exact content length when it's available.
long contentLength = 1024; // Exact size in bytes.
s3Client.putObject(req -> req
    .bucket("amzn-s3-demo-bucket")
    .key("my-key"),
RequestBody.fromInputStream(inputStream, contentLength));
```

**警告**  
 当您从输入流上传时，如果指定的内容长度与实际字节数不匹配，则您可能会遇到以下情况：  
如果指定的长度太小，则将截断对象
如果指定的长度太大，则上传失败或连接挂起

### 如果您不知道流的长度
<a name="s3-stream-upload-unknown-length"></a>

#### 使用同步 API
<a name="s3-upload-unknown-sync-client"></a>

使用 `fromContentProvider(ContentStreamProvider provider, String mimeType)`：

```
public PutObjectResponse syncClient_stream_unknown_size(String bucketName, String key, InputStream inputStream) {

    S3Client s3Client = S3Client.create();

    RequestBody body = RequestBody.fromContentProvider(ContentStreamProvider.fromInputStream(inputStream), "text/plain");
    PutObjectResponse putObjectResponse = s3Client.putObject(b -> b.bucket(BUCKET_NAME).key(KEY_NAME), body);
    return putObjectResponse;
}
```

由于 SDK 会将整个流缓冲到内存中来计算内容长度，所以在处理大型流时，可能会遇到内存不足的问题。如果您需要使用同步客户端上传大型流，请考虑使用分段 API：

##### 使用同步客户端 API 和分段 API 上传流
<a name="sync-multipart-upload-stream"></a>

```
public static void uploadStreamToS3(String bucketName, String key, InputStream inputStream) {
    // Create S3 client
    S3Client s3Client = S3Client.create();
    try {
        // Step 1: Initiate the multipart upload
        CreateMultipartUploadRequest createMultipartUploadRequest = CreateMultipartUploadRequest.builder()
                .bucket(bucketName)
                .key(key)
                .build();

        CreateMultipartUploadResponse createResponse = s3Client.createMultipartUpload(createMultipartUploadRequest);
        String uploadId = createResponse.uploadId();
        System.out.println("Started multipart upload with ID: " + uploadId);

        // Step 2: Upload parts
        List<CompletedPart> completedParts = new ArrayList<>();
        int partNumber = 1;
        byte[] buffer = new byte[PART_SIZE];
        int bytesRead;

        try {
            while ((bytesRead = readFullyOrToEnd(inputStream, buffer)) > 0) {
                // Create request to upload a part
                UploadPartRequest uploadPartRequest = UploadPartRequest.builder()
                        .bucket(bucketName)
                        .key(key)
                        .uploadId(uploadId)
                        .partNumber(partNumber)
                        .build();

                // If we didn't read a full buffer, create a properly sized byte array
                RequestBody requestBody;
                if (bytesRead < PART_SIZE) {
                    byte[] lastPartBuffer = new byte[bytesRead];
                    System.arraycopy(buffer, 0, lastPartBuffer, 0, bytesRead);
                    requestBody = RequestBody.fromBytes(lastPartBuffer);
                } else {
                    requestBody = RequestBody.fromBytes(buffer);
                }

                // Upload the part and save the response's ETag
                UploadPartResponse uploadPartResponse = s3Client.uploadPart(uploadPartRequest, requestBody);
                CompletedPart part = CompletedPart.builder()
                        .partNumber(partNumber)
                        .eTag(uploadPartResponse.eTag())
                        .build();
                completedParts.add(part);

                System.out.println("Uploaded part " + partNumber + " with size " + bytesRead + " bytes");
                partNumber++;
            }

            // Step 3: Complete the multipart upload
            CompletedMultipartUpload completedMultipartUpload = CompletedMultipartUpload.builder()
                    .parts(completedParts)
                    .build();

            CompleteMultipartUploadRequest completeRequest = CompleteMultipartUploadRequest.builder()
                    .bucket(bucketName)
                    .key(key)
                    .uploadId(uploadId)
                    .multipartUpload(completedMultipartUpload)
                    .build();

            CompleteMultipartUploadResponse completeResponse = s3Client.completeMultipartUpload(completeRequest);
            System.out.println("Multipart upload completed. Object URL: " + completeResponse.location());

        } catch (Exception e) {
            // If an error occurs, abort the multipart upload
            System.err.println("Error during multipart upload: " + e.getMessage());
            AbortMultipartUploadRequest abortRequest = AbortMultipartUploadRequest.builder()
                    .bucket(bucketName)
                    .key(key)
                    .uploadId(uploadId)
                    .build();
            s3Client.abortMultipartUpload(abortRequest);
            System.err.println("Multipart upload aborted");
        } finally {
            try {
                inputStream.close();
            } catch (IOException e) {
                System.err.println("Error closing input stream: " + e.getMessage());
            }
        }
    } finally {
        s3Client.close();
    }
}

/**
 * Reads from the input stream into the buffer, attempting to fill the buffer completely
 * or until the end of the stream is reached.
 *
 * @param inputStream the input stream to read from
 * @param buffer      the buffer to fill
 * @return the number of bytes read, or -1 if the end of the stream is reached before any bytes are read
 * @throws IOException if an I/O error occurs
 */
private static int readFullyOrToEnd(InputStream inputStream, byte[] buffer) throws IOException {
    int totalBytesRead = 0;
    int bytesRead;

    while (totalBytesRead < buffer.length) {
        bytesRead = inputStream.read(buffer, totalBytesRead, buffer.length - totalBytesRead);
        if (bytesRead == -1) {
            break; // End of stream
        }
        totalBytesRead += bytesRead;
    }

    return totalBytesRead > 0 ? totalBytesRead : -1;
}
```

**注意**  
对于大多数使用案例，建议为未知大小的流使用异步客户端 API。这种方法支持并行传输，并提供更简单的编程接口，因为如果流很大，SDK 在处理流时会将其分割成多个分段。  
启用了分段功能的标准 S3 异步客户端和基于 AWS CRT 的 S3 客户端都实现了这种方法。我们将在下一节介绍此方法的示例。

#### 使用异步 API
<a name="s3-stream-upload-unknown-async-client"></a>

您可以将 `contentLength` 参数 `null` 提供给 `fromInputStream(InputStream inputStream, Long contentLength, ExecutorService executor)`

**Example 使用基于 C AWS RT 的异步客户端：**  

```
public PutObjectResponse crtClient_stream_unknown_size(String bucketName, String key, InputStream inputStream) {

    S3AsyncClient s3AsyncClient = S3AsyncClient.crtCreate();
    ExecutorService executor = Executors.newSingleThreadExecutor();
    AsyncRequestBody body = AsyncRequestBody.fromInputStream(inputStream, null, executor);  // 'null' indicates that the
                                                                                            // content length is unknown.
    CompletableFuture<PutObjectResponse> responseFuture =
            s3AsyncClient.putObject(r -> r.bucket(bucketName).key(key), body)
                    .exceptionally(e -> {
                        if (e != null){
                            logger.error(e.getMessage(), e);
                        }
                        return null;
                    });

    PutObjectResponse response = responseFuture.join(); // Wait for the response.
    executor.shutdown();
    return response;
}
```

**Example 使用启用了分段的标准异步客户端：**  

```
public PutObjectResponse asyncClient_multipart_stream_unknown_size(String bucketName, String key, InputStream inputStream) {

    S3AsyncClient s3AsyncClient = S3AsyncClient.builder().multipartEnabled(true).build();
    ExecutorService executor = Executors.newSingleThreadExecutor();
    AsyncRequestBody body = AsyncRequestBody.fromInputStream(inputStream, null, executor); // 'null' indicates that the
                                                                                           // content length is unknown.
    CompletableFuture<PutObjectResponse> responseFuture =
            s3AsyncClient.putObject(r -> r.bucket(bucketName).key(key), body)
                    .exceptionally(e -> {
                        if (e != null) {
                            logger.error(e.getMessage(), e);
                        }
                        return null;
                    });

    PutObjectResponse response = responseFuture.join(); // Wait for the response.
    executor.shutdown();
    return response;
}
```

# 使用 Amazon S3 预签名 URLs
<a name="examples-s3-presign"></a>

预签名 URLs 提供对私有 S3 对象的临时访问权限，而无需用户拥有 AWS 证书或权限。

例如，假设 Alice 有权访问 S3 对象，并希望临时与 Bob 分享对该对象的访问权限。Alice 可以生成预签名的 GET 请求来与 Bob 分享，这样 Bob 就可以下载该对象而无需访问 Alice 的凭证。您可以 URLs 为 HTTP GET 和 HTTP PUT 请求生成预签名。

## 为对象生成预签名 URL，然后下载对象（GET 请求）
<a name="get-presignedobject"></a>

以下示例由两部分组成。
+ 第 1 部分：Alice 为对象生成预签名 URL。
+ 第 2 部分：Bob 使用预签名 URL 下载对象。

### 第 1 部分：生成 URL
<a name="get-presigned-object-part1"></a>

Alice 在 S3 桶中已有一个对象。她使用以下代码生成一个 URL 字符串，Bob 可以在后续的 GET 请求中使用该字符串。

#### 导入
<a name="get-presigned-example-imports"></a>

```
import com.example.s3.util.PresignUrlUtils;
import org.slf4j.Logger;
import software.amazon.awssdk.http.HttpExecuteRequest;
import software.amazon.awssdk.http.HttpExecuteResponse;
import software.amazon.awssdk.http.SdkHttpClient;
import software.amazon.awssdk.http.SdkHttpMethod;
import software.amazon.awssdk.http.SdkHttpRequest;
import software.amazon.awssdk.http.apache.ApacheHttpClient;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.GetObjectRequest;
import software.amazon.awssdk.services.s3.model.S3Exception;
import software.amazon.awssdk.services.s3.presigner.S3Presigner;
import software.amazon.awssdk.services.s3.presigner.model.GetObjectPresignRequest;
import software.amazon.awssdk.services.s3.presigner.model.PresignedGetObjectRequest;
import software.amazon.awssdk.utils.IoUtils;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.file.Paths;
import java.time.Duration;
import java.util.UUID;
```

```
    /* Create a pre-signed URL to download an object in a subsequent GET request. */
    public String createPresignedGetUrl(String bucketName, String keyName) {
        try (S3Presigner presigner = S3Presigner.create()) {

            GetObjectRequest objectRequest = GetObjectRequest.builder()
                    .bucket(bucketName)
                    .key(keyName)
                    .build();

            GetObjectPresignRequest presignRequest = GetObjectPresignRequest.builder()
                    .signatureDuration(Duration.ofMinutes(10))  // The URL will expire in 10 minutes.
                    .getObjectRequest(objectRequest)
                    .build();

            PresignedGetObjectRequest presignedRequest = presigner.presignGetObject(presignRequest);
            logger.info("Presigned URL: [{}]", presignedRequest.url().toString());
            logger.info("HTTP method: [{}]", presignedRequest.httpRequest().method());

            return presignedRequest.url().toExternalForm();
        }
    }
```

### 第 2 部分：下载对象
<a name="get-presigned-object-part2"></a>

Bob 使用以下三个代码选项之一来下载对象。或者，他可以使用浏览器来执行 GET 请求。

#### 使用 JDK `HttpURLConnection`（自 v1.1 起）
<a name="get-presigned-example-useHttpUrlConnection"></a>

```
    /* Use the JDK HttpURLConnection (since v1.1) class to do the download. */
    public byte[] useHttpUrlConnectionToGet(String presignedUrlString) {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); // Capture the response body to a byte array.

        try {
            URL presignedUrl = new URL(presignedUrlString);
            HttpURLConnection connection = (HttpURLConnection) presignedUrl.openConnection();
            connection.setRequestMethod("GET");
            // Download the result of executing the request.
            try (InputStream content = connection.getInputStream()) {
                IoUtils.copy(content, byteArrayOutputStream);
            }
            logger.info("HTTP response code is " + connection.getResponseCode());

        } catch (S3Exception | IOException e) {
            logger.error(e.getMessage(), e);
        }
        return byteArrayOutputStream.toByteArray();
    }
```

#### 使用 JDK `HttpClient`（自 v11 起）
<a name="get-presigned-example-useHttpClient"></a>

```
    /* Use the JDK HttpClient (since v11) class to do the download. */
    public byte[] useHttpClientToGet(String presignedUrlString) {
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); // Capture the response body to a byte array.

        HttpRequest.Builder requestBuilder = HttpRequest.newBuilder();
        HttpClient httpClient = HttpClient.newHttpClient();
        try {
            URL presignedUrl = new URL(presignedUrlString);
            HttpResponse<InputStream> response = httpClient.send(requestBuilder
                            .uri(presignedUrl.toURI())
                            .GET()
                            .build(),
                    HttpResponse.BodyHandlers.ofInputStream());

            IoUtils.copy(response.body(), byteArrayOutputStream);

            logger.info("HTTP response code is " + response.statusCode());

        } catch (URISyntaxException | InterruptedException | IOException e) {
            logger.error(e.getMessage(), e);
        }
        return byteArrayOutputStream.toByteArray();
    }
```

#### 使用 SDK for Java 中的 `SdkHttpClient`
<a name="get-presigned-example-useSdkHttpClient"></a>

```
    /* Use the AWS SDK for Java SdkHttpClient class to do the download. */
    public byte[] useSdkHttpClientToGet(String presignedUrlString) {

        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); // Capture the response body to a byte array.
        try {
            URL presignedUrl = new URL(presignedUrlString);
            SdkHttpRequest request = SdkHttpRequest.builder()
                    .method(SdkHttpMethod.GET)
                    .uri(presignedUrl.toURI())
                    .build();

            HttpExecuteRequest executeRequest = HttpExecuteRequest.builder()
                    .request(request)
                    .build();

            try (SdkHttpClient sdkHttpClient = ApacheHttpClient.create()) {
                HttpExecuteResponse response = sdkHttpClient.prepareRequest(executeRequest).call();
                response.responseBody().ifPresentOrElse(
                        abortableInputStream -> {
                            try {
                                IoUtils.copy(abortableInputStream, byteArrayOutputStream);
                            } catch (IOException e) {
                                throw new RuntimeException(e);
                            }
                        },
                        () -> logger.error("No response body."));

                logger.info("HTTP Response code is {}", response.httpResponse().statusCode());
            }
        } catch (URISyntaxException | IOException e) {
            logger.error(e.getMessage(), e);
        }
        return byteArrayOutputStream.toByteArray();
    }
```

请查看[完整的示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/d73001daea05266eaa9e074ccb71b9383832369a/javav2/example_code/s3/src/main/java/com/example/s3/GeneratePresignedGetUrlAndRetrieve.java)并[进行测试](https://github.com/awsdocs/aws-doc-sdk-examples/blob/d73001daea05266eaa9e074ccb71b9383832369a/javav2/example_code/s3/src/test/java/com/example/s3/presignurl/GeneratePresignedGetUrlTests.java) GitHub。

## 为上传生成预签名 URL，然后上传文件（PUT 请求）
<a name="put-presignedobject"></a>

以下示例由两部分组成。
+ 第 1 部分：Alice 生成用于上传对象的预签名 URL。
+ 第 2 部分：Bob 使用预签名 URL 上传文件。

### 第 1 部分：生成 URL
<a name="put-presigned-object-part1"></a>

Alice 已有一个 S3 桶。她使用以下代码生成一个 URL 字符串，Bob 可以在后续的 PUT 请求中使用该字符串。

#### 导入
<a name="put-presigned-example-imports"></a>

```
import com.example.s3.util.PresignUrlUtils;
import org.slf4j.Logger;
import software.amazon.awssdk.core.internal.sync.FileContentStreamProvider;
import software.amazon.awssdk.http.HttpExecuteRequest;
import software.amazon.awssdk.http.HttpExecuteResponse;
import software.amazon.awssdk.http.SdkHttpClient;
import software.amazon.awssdk.http.SdkHttpMethod;
import software.amazon.awssdk.http.SdkHttpRequest;
import software.amazon.awssdk.http.apache.ApacheHttpClient;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;
import software.amazon.awssdk.services.s3.model.S3Exception;
import software.amazon.awssdk.services.s3.presigner.S3Presigner;
import software.amazon.awssdk.services.s3.presigner.model.PresignedPutObjectRequest;
import software.amazon.awssdk.services.s3.presigner.model.PutObjectPresignRequest;

import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.net.HttpURLConnection;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Duration;
import java.util.Map;
import java.util.UUID;
```

```
    /* Create a presigned URL to use in a subsequent PUT request */
    public String createPresignedUrl(String bucketName, String keyName, Map<String, String> metadata) {
        try (S3Presigner presigner = S3Presigner.create()) {

            PutObjectRequest objectRequest = PutObjectRequest.builder()
                    .bucket(bucketName)
                    .key(keyName)
                    .metadata(metadata)
                    .build();

            PutObjectPresignRequest presignRequest = PutObjectPresignRequest.builder()
                    .signatureDuration(Duration.ofMinutes(10))  // The URL expires in 10 minutes.
                    .putObjectRequest(objectRequest)
                    .build();


            PresignedPutObjectRequest presignedRequest = presigner.presignPutObject(presignRequest);
            String myURL = presignedRequest.url().toString();
            logger.info("Presigned URL to upload a file to: [{}]", myURL);
            logger.info("HTTP method: [{}]", presignedRequest.httpRequest().method());

            return presignedRequest.url().toExternalForm();
        }
    }
```

### 第 2 部分：上传文件对象
<a name="put-presigned-object-part2"></a>

Bob 使用以下三个代码选项之一来上传文件。

#### 使用 JDK `HttpURLConnection`（自 v1.1 起）
<a name="put-presigned-example-useHttpUrlConnection"></a>

```
    /* Use the JDK HttpURLConnection (since v1.1) class to do the upload. */
    public void useHttpUrlConnectionToPut(String presignedUrlString, File fileToPut, Map<String, String> metadata) {
        logger.info("Begin [{}] upload", fileToPut.toString());
        try {
            URL presignedUrl = new URL(presignedUrlString);
            HttpURLConnection connection = (HttpURLConnection) presignedUrl.openConnection();
            connection.setDoOutput(true);
            metadata.forEach((k, v) -> connection.setRequestProperty("x-amz-meta-" + k, v));
            connection.setRequestMethod("PUT");
            OutputStream out = connection.getOutputStream();

            try (RandomAccessFile file = new RandomAccessFile(fileToPut, "r");
                 FileChannel inChannel = file.getChannel()) {
                ByteBuffer buffer = ByteBuffer.allocate(8192); //Buffer size is 8k

                while (inChannel.read(buffer) > 0) {
                    buffer.flip();
                    for (int i = 0; i < buffer.limit(); i++) {
                        out.write(buffer.get());
                    }
                    buffer.clear();
                }
            } catch (IOException e) {
                logger.error(e.getMessage(), e);
            }

            out.close();
            connection.getResponseCode();
            logger.info("HTTP response code is " + connection.getResponseCode());

        } catch (S3Exception | IOException e) {
            logger.error(e.getMessage(), e);
        }
    }
```

#### 使用 JDK `HttpClient`（自 v11 起）
<a name="put-presigned-example-useHttpClient"></a>

```
    /* Use the JDK HttpClient (since v11) class to do the upload. */
    public void useHttpClientToPut(String presignedUrlString, File fileToPut, Map<String, String> metadata) {
        logger.info("Begin [{}] upload", fileToPut.toString());

        HttpRequest.Builder requestBuilder = HttpRequest.newBuilder();
        metadata.forEach((k, v) -> requestBuilder.header("x-amz-meta-" + k, v));

        HttpClient httpClient = HttpClient.newHttpClient();
        try {
            final HttpResponse<Void> response = httpClient.send(requestBuilder
                            .uri(new URL(presignedUrlString).toURI())
                            .PUT(HttpRequest.BodyPublishers.ofFile(Path.of(fileToPut.toURI())))
                            .build(),
                    HttpResponse.BodyHandlers.discarding());

            logger.info("HTTP response code is " + response.statusCode());

        } catch (URISyntaxException | InterruptedException | IOException e) {
            logger.error(e.getMessage(), e);
        }
    }
```

#### 使用 SDK for Java 中的 `SdkHttpClient`
<a name="put-presigned-example-useSdkHttpClient"></a>

```
    /* Use the AWS SDK for Java V2 SdkHttpClient class to do the upload. */
    public void useSdkHttpClientToPut(String presignedUrlString, File fileToPut, Map<String, String> metadata) {
        logger.info("Begin [{}] upload", fileToPut.toString());

        try {
            URL presignedUrl = new URL(presignedUrlString);

            SdkHttpRequest.Builder requestBuilder = SdkHttpRequest.builder()
                    .method(SdkHttpMethod.PUT)
                    .uri(presignedUrl.toURI());
            // Add headers
            metadata.forEach((k, v) -> requestBuilder.putHeader("x-amz-meta-" + k, v));
            // Finish building the request.
            SdkHttpRequest request = requestBuilder.build();

            HttpExecuteRequest executeRequest = HttpExecuteRequest.builder()
                    .request(request)
                    .contentStreamProvider(new FileContentStreamProvider(fileToPut.toPath()))
                    .build();

            try (SdkHttpClient sdkHttpClient = ApacheHttpClient.create()) {
                HttpExecuteResponse response = sdkHttpClient.prepareRequest(executeRequest).call();
                logger.info("Response code: {}", response.httpResponse().statusCode());
            }
        } catch (URISyntaxException | IOException e) {
            logger.error(e.getMessage(), e);
        }
    }
```

请查看[完整的示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/d73001daea05266eaa9e074ccb71b9383832369a/javav2/example_code/s3/src/main/java/com/example/s3/GeneratePresignedUrlAndPutFileWithMetadata.java)并[进行测试](https://github.com/awsdocs/aws-doc-sdk-examples/blob/d73001daea05266eaa9e074ccb71b9383832369a/javav2/example_code/s3/src/test/java/com/example/s3/presignurl/GeneratePresignedPutUrlTests.java) GitHub。

# Amazon S3 的跨区域访问
<a name="s3-cross-region"></a>

您在使用 Amazon Simple Storage Service (Amazon S3) 桶时，通常会知道桶的 AWS 区域。您使用的区域是在您创建 S3 客户端时确定的。

但有时，您可能需要使用一个特定的桶，但您不知道该桶是否位于为 S3 客户端设置的区域中。

您可以使用 SDK 启用跨不同区域访问 S3 桶的功能，而不必进行更多调用来确定桶区域。

## 设置
<a name="s3-cross-region-setup"></a>

SDK 版本 `2.20.111` 已支持跨区域访问。请在 Maven 构建文件中使用此版本或更高版本作为 `s3` 依赖项，如以下代码段所示。

```
<dependency>
  <groupId>software.amazon.awssdk</groupId>
  <artifactId>s3</artifactId>
  <version>2.27.21</version>
</dependency>
```

接下来，在创建 S3 客户端时，启用跨区域访问，如代码段所示。默认情况下，未启用此访问。

```
S3AsyncClient client = S3AsyncClient.builder()
                                    .crossRegionAccessEnabled(true)
                                    .build();
```

## SDK 如何提供跨区域访问
<a name="s3-cross-region-routing"></a>

当您在请求中引用现有桶时（例如使用 `putObject` 方法），SDK 会向为客户端配置的区域发起请求。

如果该特定区域中不存在该桶，则错误响应将包括该桶所在的实际区域。然后，SDK 在第二个请求中使用正确的区域。

为了优化将来对同一个桶的请求，SDK 会在客户端中缓存此区域映射。

## 注意事项
<a name="s3-cross-region-considerations"></a>

启用跨区域桶访问时，请注意，如果桶不在客户端配置的区域中，则可能会导致第一次 API 调用延迟增加。但是，后续调用会受益于缓存的区域信息，从而提高性能。

启用跨区域访问后，对桶的访问权限不会受到影响。无论桶位于哪个区域，用户都必须获得访问桶的授权。

# 使用校验和实现数据完整性保护
<a name="s3-checksums"></a>

Amazon Simple Storage Service (Amazon S3) 允许您在上传对象时指定校验和。当您指定校验和时，校验和与对象一起存储，并且可以在下载对象时验证该校验和。

传输文件时，校验和可提供额外的数据层完整性。使用校验和，您可以通过确认收到文件与原始文件是否匹配来验证数据一致性。有关 Amazon S3 校验和的更多信息，请参阅 [Amazon Simple Storage Service 用户指南](https://docs.aws.amazon.com/AmazonS3/latest/userguide/checking-object-integrity.html)，包括[支持的算法](https://docs.aws.amazon.com/AmazonS3/latest/userguide/checking-object-integrity.html#using-additional-checksums)。

您可以灵活地选择最适合自己需求的算法，并让 SDK 计算校验和。或者，您也可以使用任一受支持的算法，提供预先计算好的校验和值。

**注意**  
从 AWS SDK for Java 2.x的版本 2.30.0 开始，SDK 通过自动计算上传文件的 `CRC32` 校验和来提供默认的完整性保护。如果您未提供预先计算的校验和值，或者没有指定 SDK 计算校验和时应使用的算法，SDK 就会计算此校验和。   
SDK 还提供数据完整性保护的全局设置，您可以在外部进行设置，您可以在[AWS SDKs 和工具参考指南](https://docs.aws.amazon.com/sdkref/latest/guide/feature-dataintegrity.html)中阅读这些设置。

我们在两个请求阶段讨论校验和：上传对象和下载对象。

## 上传对象
<a name="use-service-S3-checksum-upload"></a>

使用 `putObject` 方法上传对象并提供校验和算法时，SDK 会计算指定算法的校验和。

以下代码段展示了上传具有 `SHA256` 校验和的对象的请求。当 SDK 发送请求时，它会计算 `SHA256` 校验和并上传对象。Amazon S3 会计算校验和并将其与 SDK 提供的校验和进行对比，从而验证内容的完整性。然后 Amazon S3 将校验和与对象一起存储。

```
public void putObjectWithChecksum() {
        s3Client.putObject(b -> b
                .bucket(bucketName)
                .key(key)
                .checksumAlgorithm(ChecksumAlgorithm.SHA256),
            RequestBody.fromString("This is a test"));
}
```

如果您未在请求中提供校验和算法，则校验和行为将根据您使用的 SDK 版本而异，具体如下表所示。

**未提供校验和算法时的校验和行为**


| Java SDK 版本 | 校验和行为 | 
| --- | --- | 
| 2.30.0 之前的版本 | SDK 不会自动计算基于 CRC 的校验和，也不会在请求中提供该值。 | 
| 2.30.0 或更高版本 | SDK 使用 `CRC32` 算法计算校验和，并在请求中提供该值。Amazon S3 通过计算自己的 `CRC32` 校验和并将其与 SDK 提供的校验和进行对比，从而验证传输的完整性。如果校验和匹配，则校验和将与对象一起保存。 | 

### 使用预先计算的校验和值
<a name="use-service-S3-checksum-upload-pre"></a>

与请求一起提供的预先计算校验和值会禁用 SDK 的自动计算，而是使用提供的值。

以下示例显示了具有预先计算的 SHA256校验和的请求。

```
    public void putObjectWithPrecalculatedChecksum(String filePath) {
        String checksum = calculateChecksum(filePath, "SHA-256");

        s3Client.putObject((b -> b
                .bucket(bucketName)
                .key(key)
                .checksumSHA256(checksum)),
            RequestBody.fromFile(Paths.get(filePath)));
    }
```

如果 Amazon S3 确定指定算法的校验和值不正确，服务就会返回错误响应。

### 分段上传
<a name="use-service-S3-checksum-upload-multi"></a>

您也可以将校验和用于分段上传。

 适用于 Java 2.x 的 SDK 提供了两个选项，用于在分段上传中使用校验和。第一个选项使用 `S3TransferManager`。

以下传输管理器示例指定了上传 SHA1 算法。

```
    public void multipartUploadWithChecksumTm(String filePath) {
        S3TransferManager transferManager = S3TransferManager.create();
        UploadFileRequest uploadFileRequest = UploadFileRequest.builder()
            .putObjectRequest(b -> b
                .bucket(bucketName)
                .key(key)
                .checksumAlgorithm(ChecksumAlgorithm.SHA1))
            .source(Paths.get(filePath))
            .build();
        FileUpload fileUpload = transferManager.uploadFile(uploadFileRequest);
        fileUpload.completionFuture().join();
        transferManager.close();
    }
```

如果您在使用传输管理器上传时未提供校验和算法，则 SDK 会根据 `CRC32` 算法自动计算校验和。SDK 会对所有版本的 SDK 执行此计算。

第二个选项使用 [`S3Client` API](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3Client.html)（或 [`S3AsyncClient` API](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3AsyncClient.html)）来执行分段上传。如果用此方法来指定校验和，则您必须指定在启动上传时使用的算法。您还必须为每个分段请求指定算法，并提供每个分段在上传后计算得出的校验和。

```
    public void multipartUploadWithChecksumS3Client(String filePath) {
        ChecksumAlgorithm algorithm = ChecksumAlgorithm.CRC32;

        // Initiate the multipart upload.
        CreateMultipartUploadResponse createMultipartUploadResponse = s3Client.createMultipartUpload(b -> b
            .bucket(bucketName)
            .key(key)
            .checksumAlgorithm(algorithm)); // Checksum specified on initiation.
        String uploadId = createMultipartUploadResponse.uploadId();

        // Upload the parts of the file.
        int partNumber = 1;
        List<CompletedPart> completedParts = new ArrayList<>();
        ByteBuffer bb = ByteBuffer.allocate(1024 * 1024 * 5); // 5 MB byte buffer

        try (RandomAccessFile file = new RandomAccessFile(filePath, "r")) {
            long fileSize = file.length();
            long position = 0;
            while (position < fileSize) {
                file.seek(position);
                long read = file.getChannel().read(bb);

                bb.flip(); // Swap position and limit before reading from the buffer.
                UploadPartRequest uploadPartRequest = UploadPartRequest.builder()
                    .bucket(bucketName)
                    .key(key)
                    .uploadId(uploadId)
                    .checksumAlgorithm(algorithm) // Checksum specified on each part.
                    .partNumber(partNumber)
                    .build();

                UploadPartResponse partResponse = s3Client.uploadPart(
                    uploadPartRequest,
                    RequestBody.fromByteBuffer(bb));

                CompletedPart part = CompletedPart.builder()
                    .partNumber(partNumber)
                    .checksumCRC32(partResponse.checksumCRC32()) // Provide the calculated checksum.
                    .eTag(partResponse.eTag())
                    .build();
                completedParts.add(part);

                bb.clear();
                position += read;
                partNumber++;
            }
        } catch (IOException e) {
            System.err.println(e.getMessage());
        }

        // Complete the multipart upload.
        s3Client.completeMultipartUpload(b -> b
            .bucket(bucketName)
            .key(key)
            .uploadId(uploadId)
            .multipartUpload(CompletedMultipartUpload.builder().parts(completedParts).build()));
    }
```

[完整示例和[测试](https://github.com/awsdocs/aws-doc-sdk-examples/blob/main/javav2/example_code/s3/src/test/java/com/example/s3/PerformMultiPartUploadTests.java)的代码](https://github.com/awsdocs/aws-doc-sdk-examples/blob/main/javav2/example_code/s3/src/main/java/com/example/s3/PerformMultiPartUpload.java)位于 GitHub 代码示例存储库中。

## 下载对象
<a name="use-service-S3-checksum-download"></a>

使用 [getObject](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3Client.html#getObject(software.amazon.awssdk.services.s3.model.GetObjectRequest)) 方法下载对象时，在以下情况下 SDK 会自动验证校验和：如果适用于 `GetObjectRequest` 的生成器的 `checksumMode` 方法设置为 `ChecksumMode.ENABLED`。

以下代码段中的请求引导 SDK 通过计算校验和并比较值来验证响应中的校验和。

```
    public GetObjectResponse getObjectWithChecksum() {
        return s3Client.getObject(b -> b
                        .bucket(bucketName)
                        .key(key)
                        .checksumMode(ChecksumMode.ENABLED))
                .response();
    }
```

**注意**  
如果上传对象时没有使用校验和，则不会进行验证。

## 其他校验和计算选项
<a name="S3-checsum-calculation-options"></a>

**注意**  
为了验证已传输数据的数据完整性以及识别任何传输错误，我们建议用户为校验和计算选项保留 SDK 默认设置。默认情况下，SDK 会为许多 S3 操作（包括 `PutObject` 和 `GetObject`）添加这项重要检查。

但如果您希望在使用 Amazon S3 时仅进行最少的校验和验证，则可以通过更改默认配置设置来禁用许多检查。

### 除非必要，否则请禁用自动校验和计算
<a name="S3-minimize-checksum-calc-global"></a>

对于支持校验和的操作（例如 `PutObject` 和 `GetObject`），您可以禁用 SDK 的自动校验和计算。但某些 S3 操作需要校验和计算；您不能为这些操作禁用校验和计算。

SDK 为请求的有效载荷和响应的有效载荷的校验和计算提供了单独的设置。

下面的列表说明了可用于在不同范围内更大限度地减少校验和计算的设置。
+ **所有应用程序范围**-通过更改环境变量或共享 AWS `config`文件中的配置`credentials`文件中的设置，所有应用程序都可以使用这些设置。除非在应用程序或服务客户端范围内被覆盖，否则这些设置会影响所有 AWS SDK 应用程序中的所有服务客户端。
  + 在配置文件中添加设置：

    ```
    [default]
    request_checksum_calculation = WHEN_REQUIRED
    response_checksum_validation = WHEN_REQUIRED
    ```
  + 添加环境变量：

    ```
    AWS_REQUEST_CHECKSUM_CALCULATION=WHEN_REQUIRED
    AWS_RESPONSE_CHECKSUM_VALIDATION=WHEN_REQUIRED
    ```
+ **当前应用程序范围** - 您可以将 Java 系统属性 `aws.requestChecksumCalculation` 设置为 `WHEN_REQUIRED`，以便限制校验和计算。响应的相应系统属性为 `aws.responseChecksumValidation`。

  除非在服务客户端创建期间已覆盖，否则这些设置会影响应用程序中的所有 SDK 服务客户端。

  在应用程序启动时设置系统属性：

  ```
  import software.amazon.awssdk.core.SdkSystemSetting;
  import software.amazon.awssdk.core.checksums.RequestChecksumCalculation;
  import software.amazon.awssdk.core.checksums.ResponseChecksumValidation;
  import software.amazon.awssdk.services.s3.S3Client;
  
  class DemoClass {
      public static void main(String[] args) {
  
          System.setProperty(SdkSystemSetting.AWS_REQUEST_CHECKSUM_CALCULATION.property(), // Resolves to "aws.requestChecksumCalculation".
                  "WHEN_REQUIRED");
          System.setProperty(SdkSystemSetting.AWS_RESPONSE_CHECKSUM_VALIDATION.property(), // Resolves to "aws.responseChecksumValidation".
                  "WHEN_REQUIRED");
  
          S3Client s3Client = S3Client.builder().build();
  
          // Use s3Client.
      }
  }
  ```
+ **单个 S3 服务客户端范围** - 您可以使用生成器方法配置单个 S3 服务客户端，使其仅计算最少必要数量的校验和：

  ```
  import software.amazon.awssdk.core.checksums.RequestChecksumCalculation;
  import software.amazon.awssdk.services.s3.S3Client;
  
  public class RequiredChecksums {
      public static void main(String[] args) {
          S3Client s3 = S3Client.builder()
                  .requestChecksumCalculation(RequestChecksumCalculation.WHEN_REQUIRED)
                  .responseChecksumValidation(ResponseChecksumValidation.WHEN_REQUIRED)
                  .build();
  
          // Use s3Client. 
      }
  // ...
  }
  ```

### 使用以简`LegacyMd5Plugin`化 MD5 兼容性
<a name="S3-checksum-legacy-md5"></a>

随着 2.30.0 版中 CRC32 校验和行为的发布，SDK 停止计算所需操作的 MD5 校验和。

如果您需要对 S3 操作进行传统 MD5 校验和行为，则可以使用 SDK 版本 2.31.32 中发布的。`LegacyMd5Plugin`

当您需要保持与依赖传统 MD5 校验和行为的应用程序的兼容性时，尤其`LegacyMd5Plugin`是在使用与 S3 兼容的第三方存储提供程序（例如与 S3A 文件系统连接器（Apache Spark、Iceberg）一起使用的存储提供程序时，尤其如此。

要使用 `LegacyMd5Plugin`，请将其添加到 S3 客户端生成器中：

```
// For synchronous S3 client.
S3Client s3Client = S3Client.builder()
                           .addPlugin(LegacyMd5Plugin.create())
                           .build();

// For asynchronous S3 client.
S3AsyncClient asyncClient = S3AsyncClient.builder()
                                       .addPlugin(LegacyMd5Plugin.create())
                                       .build();
```

如果要向需要 MD5 校验和的操作添加校验和，并希望跳过为支持校验和但不是必需的操作添加 SDK 默认校验和，则可以启用选项和 as。`ClientBuilder` `requestChecksumCalculation` `responseChecksumValidation` `WHEN_REQUIRED`这样就只会将 SDK 默认校验和添加到需要校验和的操作中：

```
// Use the `LegacyMd5Plugin` with `requestChecksumCalculation` and `responseChecksumValidation` set to WHEN_REQUIRED.
S3AsyncClient asyncClient = S3AsyncClient.builder()
                                       .addPlugin(LegacyMd5Plugin.create())
                                       .requestChecksumCalculation(RequestChecksumCalculation.WHEN_REQUIRED)
                                       .responseChecksumValidation(ResponseChecksumValidation.WHEN_REQUIRED)
                                       .build();
```

当使用与 S3 兼容的第三方存储系统时，此配置特别有用，这些存储系统可能不完全支持较新的校验和算法，但某些操作仍需要 MD5 校验和。

# 使用高性能 S3 客户端： AWS 基于 CRT 的 S3 客户端
<a name="crt-based-s3-client"></a>

 AWS 基于 CRT 的 S3 客户端（建立在[AWS 公共运行时 (CRT) 之上）](https://docs.aws.amazon.com/sdkref/latest/guide/common-runtime.html)是替代的 S3 异步客户端。它通过自动使用 Amazon S3 的[分段上传 API](https://docs.aws.amazon.com/AmazonS3/latest/userguide/mpuoverview.html) 和[字节范围提取](https://docs.aws.amazon.com/AmazonS3/latest/userguide/optimizing-performance-guidelines.html#optimizing-performance-guidelines-get-range)，将对象传入或传出 Amazon Simple Storage Service (Amazon S3)，从而提高了性能和可靠性。

 AWS 基于 CRT 的 S3 客户端可提高网络故障时的传输可靠性。通过重试文件传输中失败的各个分段，而无需从头开始重新启动传输，从而提高可靠性。

此外， AWS 基于 CRT 的 S3 客户端还提供了增强的连接池和域名系统 (DNS) 负载平衡，这也提高了吞吐量。

您可以使用 AWS 基于 CRT 的 S3 客户端来代替 SDK 的标准 S3 异步客户端，并立即利用其提高的吞吐量。

**重要**  
 AWS 基于 CRT 的 S3 客户端目前不支持在客户端级别和请求级别[收集 SDK 指标](metrics.md)。

**AWS SDK 中基于 CRT 的组件**

本主题中介绍 AWS 的基于 CRT 的 *S3* 客户端和 AWS 基于 CRT 的 *HTTP* 客户端是软件开发工具包中的不同组件。

**AWS 基于 CRT 的 S3 客户端**是 S [3 AsyncClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3AsyncClient.html) 接口的实现，用于与 Amazon S3 服务配合使用。它是基于 Java 的 `S3AsyncClient` 接口实现的替代方案，具有多种优势。

[AWS 基于 CRT 的 HTTP 客户端](http-configuration-crt.md)是该[SdkAsyncHttpClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/http/async/SdkAsyncHttpClient.html)接口的实现，用于一般的 HTTP 通信。它是 Netty `SdkAsyncHttpClient` 接口实现的替代方案，具有多种优势。

尽管两个组件都使用[AWS 公共运行时](https://docs.aws.amazon.com/sdkref/latest/guide/common-runtime.html)中的库，但 AWS 基于 CRT 的 S3 客户端使用 [aws-c-s3 库](https://github.com/awslabs/aws-c-s3)并支持 [S3 分段上传 API](https://docs.aws.amazon.com/AmazonS3/latest/userguide/mpuoverview.html) 功能。由于 AWS 基于 CRT 的 HTTP 客户端仅供一般用途，因此它不支持 S3 分段上传 API 功能。

## 添加依赖项以使用 AWS 基于 CRT 的 S3 客户端
<a name="crt-based-s3-client-depend"></a>

要使用 AWS 基于 CRT 的 S3 客户端，请将以下两个依赖项添加到您的 Maven 项目文件中。示例显示了要使用的最低版本。在 Maven Central 存储库中搜索 [s3](https://central.sonatype.com/artifact/software.amazon.awssdk/s3) 和 [aws-crt](https://central.sonatype.com/artifact/software.amazon.awssdk.crt/aws-crt) 构件的最新版本。

```
<dependency>
  <groupId>software.amazon.awssdk</groupId>
  <artifactId>s3</artifactId>
  <version>2.27.21</version>
</dependency>
<dependency>
  <groupId>software.amazon.awssdk.crt</groupId>
  <artifactId>aws-crt</artifactId>
  <version>0.30.11</version>
</dependency>
```

## 创建 AWS 基于 CRT 的 S3 客户端的实例
<a name="crt-based-s3-client-create"></a>

 使用默认设置创建 AWS 基于 CRT 的 S3 客户端的实例，如以下代码片段所示。

```
S3AsyncClient s3AsyncClient = S3AsyncClient.crtCreate();
```

要配置客户端，请使用 AWS CRT 客户端生成器。您可以通过更改构建器方法从标准 S3 异步客户端切换到 AWS 基于 CRT 的客户端。

```
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3AsyncClient;


S3AsyncClient s3AsyncClient = 
        S3AsyncClient.crtBuilder()
                     .credentialsProvider(DefaultCredentialsProvider.create())
                     .region(Region.US_WEST_2)
                     .targetThroughputInGbps(20.0)
                     .minimumPartSizeInBytes(8 * 1025 * 1024L)
                     .build();
```

**注意**  
 AWS CRT 客户端生成器目前可能不支持标准生成器中的某些设置。通过调用 `S3AsyncClient#builder()` 获取标准生成器。

## 使用 AWS 基于 CRT 的 S3 客户端
<a name="crt-based-s3-client-use"></a>

使用 AWS 基于 CRT 的 S3 客户端调用 Amazon S3 API 操作。以下示例演示了可通过提供的[PutObject](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3AsyncClient.html#putObject(java.util.function.Consumer,software.amazon.awssdk.core.async.AsyncRequestBody))和[GetObject](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3AsyncClient.html#getObject(java.util.function.Consumer,software.amazon.awssdk.core.async.AsyncResponseTransformer))操作 适用于 Java 的 AWS SDK。

```
import software.amazon.awssdk.core.async.AsyncRequestBody;
import software.amazon.awssdk.core.async.AsyncResponseTransformer;
import software.amazon.awssdk.services.s3.S3AsyncClient;
import software.amazon.awssdk.services.s3.model.GetObjectResponse;
import software.amazon.awssdk.services.s3.model.PutObjectResponse;


S3AsyncClient s3Client = S3AsyncClient.crtCreate();

// Upload a local file to Amazon S3.
PutObjectResponse putObjectResponse = 
      s3Client.putObject(req -> req.bucket(<BUCKET_NAME>)
                                   .key(<KEY_NAME>),
                        AsyncRequestBody.fromFile(Paths.get(<FILE_NAME>)))
              .join();

// Download an object from Amazon S3 to a local file.
GetObjectResponse getObjectResponse = 
      s3Client.getObject(req -> req.bucket(<BUCKET_NAME>)
                                   .key(<KEY_NAME>),
                        AsyncResponseTransformer.toFile(Paths.get(<FILE_NAME>)))
              .join();
```

## 上传未知大小的流
<a name="crt-stream-unknown-size"></a>

 AWS AWS 基于 CRT 的 S3 客户端的一个显著优势是它能够高效处理大小未知的输入流。当您需要从无法事先确定总大小的来源上传数据时，这特别有用。

```
public PutObjectResponse crtClient_stream_unknown_size(String bucketName, String key, InputStream inputStream) {

    S3AsyncClient s3AsyncClient = S3AsyncClient.crtCreate();
    ExecutorService executor = Executors.newSingleThreadExecutor();
    AsyncRequestBody body = AsyncRequestBody.fromInputStream(inputStream, null, executor);  // 'null' indicates that the
                                                                                            // content length is unknown.
    CompletableFuture<PutObjectResponse> responseFuture =
            s3AsyncClient.putObject(r -> r.bucket(bucketName).key(key), body)
                    .exceptionally(e -> {
                        if (e != null){
                            logger.error(e.getMessage(), e);
                        }
                        return null;
                    });

    PutObjectResponse response = responseFuture.join(); // Wait for the response.
    executor.shutdown();
    return response;
}
```

此功能有助于避免传统上传中的常见问题，即不正确的内容长度规格会导致对象被截断或上传失败。

## 配置限制
<a name="crt-based-s3-client-limitations"></a>

 AWS 基于 CRT 的 S3 客户端和基于 Java 的 S3 异步客户端[提供了类似的功能](examples-s3.md#s3-clients)，而 AWS 基于 CRT 的 S3 客户端则提供了性能优势。但是， AWS 基于 CRT 的 S3 客户端缺少基于 Java 的 S3 异步客户端所具有的配置设置。这些设置包括：
+ *客户端级别配置：*API 调用尝试超时、压缩执行拦截器、指标发布者、自定义执行属性、自定义高级选项、自定义计划执行器服务、自定义标头
+ *请求级别配置：*自定义签名者、API 调用尝试超时

有关配置差异的完整列表，请参阅 API 参考。


| 基于 Java 的 S3 异步客户端 | AWS 基于CRT的S3客户端 | 
| --- | --- | 
| 客户端级别配置[\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_cn/sdk-for-java/latest/developer-guide/crt-based-s3-client.html)请求级别配置[\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_cn/sdk-for-java/latest/developer-guide/crt-based-s3-client.html) | 客户端级别配置[\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_cn/sdk-for-java/latest/developer-guide/crt-based-s3-client.html)请求级别配置[\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_cn/sdk-for-java/latest/developer-guide/crt-based-s3-client.html) | 

# 将基于 Java 的 S3 异步客户端配置为使用并行传输
<a name="s3-async-client-multipart"></a>

从版本 2.27.5 开始，基于 Java 的标准 S3 异步客户端支持自动并行传输（分段上传和下载）。在创建基于 Java 的 S3 异步客户端时，您可以配置对并行传输的支持。

本节介绍如何启用并行传输以及如何自定义配置。

## 创建 `S3AsyncClient` 的实例
<a name="s3-async-client-multipart-create"></a>

当您不在[生成器](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/S3AsyncClientBuilder.html)上调用任何 `multipart*` 方法的情况下创建 `S3AsyncClient` 实例时，不会启用并行传输。以下每条语句都创建一个基于 Java 的 S3 异步客户端，不支持分段上传和下载。

### 在*没有*分段支持的情况下创建
<a name="s3-async-client-mp-off"></a>

**Example**  

```
import software.amazon.awssdk.auth.credentials.ProcessCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3AsyncClient;


S3AsyncClient s3Client = S3AsyncClient.create();

S3AsyncClient s3Client2 = S3AsyncClient.builder().build();

S3AsyncClient s3Client3 = S3AsyncClient.builder()
        .credentialsProvider(ProcessCredentialsProvider.builder().build())
        .region(Region.EU_NORTH_1)
        .build();
```

### 在*有*分段支持的情况下创建
<a name="s3-async-client-mp-on"></a>

要使用默认设置启用并行传输，请在生成器上调用 `multipartEnabled` 并传入 `true`，如下例所示。

**Example**  

```
S3AsyncClient s3AsyncClient2 = S3AsyncClient.builder()
        .multipartEnabled(true)
        .build();
```

`thresholdInBytes` 和 `minimumPartSizeInBytes` 设置的默认值为 8 MiB。

如果您自定义分段设置，则会自动启用并行传输，如下所示。

**Example**  

```
import software.amazon.awssdk.services.s3.S3AsyncClient;
import static software.amazon.awssdk.transfer.s3.SizeConstant.MB;


S3AsyncClient s3AsyncClient2 = S3AsyncClient.builder()
        .multipartConfiguration(b -> b
                .thresholdInBytes(16 * MB)
                .minimumPartSizeInBytes(10 * MB))
        .build();
```

## 上传未知大小的流
<a name="java-async-client-stream-unknown-size"></a>

启用了分段功能的基于 Java 的 S3 异步客户端可以有效地处理事先不知道总大小的输入流：

```
public PutObjectResponse asyncClient_multipart_stream_unknown_size(String bucketName, String key, InputStream inputStream) {

    S3AsyncClient s3AsyncClient = S3AsyncClient.builder().multipartEnabled(true).build();
    ExecutorService executor = Executors.newSingleThreadExecutor();
    AsyncRequestBody body = AsyncRequestBody.fromInputStream(inputStream, null, executor); // 'null' indicates that the
                                                                                           // content length is unknown.
    CompletableFuture<PutObjectResponse> responseFuture =
            s3AsyncClient.putObject(r -> r.bucket(bucketName).key(key), body)
                    .exceptionally(e -> {
                        if (e != null) {
                            logger.error(e.getMessage(), e);
                        }
                        return null;
                    });

    PutObjectResponse response = responseFuture.join(); // Wait for the response.
    executor.shutdown();
    return response;
}
```

这种方法可以防止手动指定错误的内容长度时可能出现的问题，例如对象截断或上传失败。

# 使用 Amazon S3 Transfer Manager 传输文件和目录
<a name="transfer-manager"></a>

Amazon S3 Transfer Manager 是针对 AWS SDK for Java 2.x的一款开源高级文件传输工具。可以使用它将文件和目录传入和传出 Amazon Simple Storage Service (Amazon S3)。

在[基于AWS CRT 的 S3 客户端](crt-based-s3-client.md)或[启用了分段的基于 Java 的标准 S3 异步客户端](s3-async-client-multipart.md)基础上构建时，S3 Transfer Manager 可以利用[分段上传 API](https://docs.aws.amazon.com/AmazonS3/latest/userguide/mpuoverview.html) 和[字节范围提取](https://docs.aws.amazon.com/AmazonS3/latest/userguide/optimizing-performance-guidelines.html#optimizing-performance-guidelines-get-range)等性能改进。

使用 S3 Transfer Manager，您还可以实时监控传输进度，并暂停传输以便日后执行。

## 开始使用
<a name="transfer-manager-prerequisites"></a>

### 将依赖项添加到构建文件中
<a name="transfer-manager-add-dependency"></a>

要在使用 S3 Transfer Manager 时获得增强的分段性能，请使用必要的依赖项配置构建文件。

------
#### [ Use the AWS CRT-based S3 client ]

```
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>software.amazon.awssdk</groupId>
            <artifactId>bom</artifactId>
            <version>2.27.211</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
<dependencies>
    <dependency>
        <groupId>software.amazon.awssdk</groupId>
        <artifactId>s3-transfer-manager</artifactId>
    </dependency>
    <dependency>
        <groupId>software.amazon.awssdk.crt</groupId>
        <artifactId>aws-crt</artifactId>
        <version>0.29.1432</version>
    </dependency>
</dependencies>
```

1 [最新版本](https://central.sonatype.com/artifact/software.amazon.awssdk/bom)。2[最新版本](https://central.sonatype.com/artifact/software.amazon.awssdk.crt/aws-crt)。

------
#### [ Use the Java-based S3 async client ]

```
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>software.amazon.awssdk</groupId>
            <artifactId>bom</artifactId>
            <version>2.27.211</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
<dependencies>
    <dependency>
        <groupId>software.amazon.awssdk</groupId>
        <artifactId>s3-transfer-manager</artifactId>
    </dependency>
</dependencies>
```

1 [最新版本](https://central.sonatype.com/artifact/software.amazon.awssdk/bom)。

------

### 创建 S3 Transfer Manager 的实例
<a name="transfer-manager-create"></a>

要启用并行传输，必须传入 AWS 基于 CRT 的 S3 客户端或启用了多部分功能的基于 Java 的 S3 异步客户端。以下示例演示如何使用自定义设置来配置 S3 Transfer Manager。

------
#### [ Use the AWS CRT-based S3 client ]

```
        S3AsyncClient s3AsyncClient = S3AsyncClient.crtBuilder()
                .credentialsProvider(DefaultCredentialsProvider.create())
                .region(Region.US_EAST_1)
                .targetThroughputInGbps(20.0)
                .minimumPartSizeInBytes(8 * MB)
                .build();

        S3TransferManager transferManager = S3TransferManager.builder()
                .s3Client(s3AsyncClient)
                .build();
```

------
#### [ Use the Java-based S3 async client ]

如果构建文件中未包含 `aws-crt` 依赖项，则 S3 Transfer Manager 将在适用于 Java 的 SDK 2.x 中使用的基于 Java 的标准 S3 异步客户端的基础上构建。

**S3 客户端的自定义配置 - 需要启用分段功能**

```
        S3AsyncClient s3AsyncClient = S3AsyncClient.builder()
                .multipartEnabled(true)
                .credentialsProvider(DefaultCredentialsProvider.create())
                .region(Region.US_EAST_1)
                .build();

        S3TransferManager transferManager = S3TransferManager.builder()
                .s3Client(s3AsyncClient)
                .build();
```

**没有 S3 客户端配置 - 自动启用分段支持**

```
S3TransferManager transferManager = S3TransferManager.create();
```

------

## 将文件上传到 S3 桶
<a name="transfer-manager-upload"></a>

以下示例显示了文件上传示例 [LoggingTransferListener](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/transfer/s3/progress/LoggingTransferListener.html)，以及记录上传进度的可选用法。

要使用 S3 Transfer Manager 将文件上传到 Amazon S3，请将 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/transfer/s3/model/UploadFileRequest.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/transfer/s3/model/UploadFileRequest.html) 对象传递给 `S3TransferManager` 的 [uploadFile](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/transfer/s3/S3TransferManager.html#uploadFile(software.amazon.awssdk.transfer.s3.model.UploadFileRequest)) 方法。

从该`uploadFile`方法返回的[FileUpload](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/transfer/s3/model/FileUpload.html)对象表示上传过程。请求完成后，该[CompletedFileUpload](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/transfer/s3/model/CompletedFileUpload.html)对象将包含有关上传的信息。

```
    public void trackUploadFile(S3TransferManager transferManager, String bucketName,
                             String key, URI filePathURI) {
        UploadFileRequest uploadFileRequest = UploadFileRequest.builder()
                .putObjectRequest(b -> b.bucket(bucketName).key(key))
                .addTransferListener(LoggingTransferListener.create())  // Add listener.
                .source(Paths.get(filePathURI))
                .build();

        FileUpload fileUpload = transferManager.uploadFile(uploadFileRequest);

        fileUpload.completionFuture().join();
        /*
            The SDK provides a LoggingTransferListener implementation of the TransferListener interface.
            You can also implement the interface to provide your own logic.

            Configure log4J2 with settings such as the following.
                <Configuration status="WARN">
                    <Appenders>
                        <Console name="AlignedConsoleAppender" target="SYSTEM_OUT">
                            <PatternLayout pattern="%m%n"/>
                        </Console>
                    </Appenders>

                    <Loggers>
                        <logger name="software.amazon.awssdk.transfer.s3.progress.LoggingTransferListener" level="INFO" additivity="false">
                            <AppenderRef ref="AlignedConsoleAppender"/>
                        </logger>
                    </Loggers>
                </Configuration>

            Log4J2 logs the progress. The following is example output for a 21.3 MB file upload.
                Transfer initiated...
                |                    | 0.0%
                |====                | 21.1%
                |============        | 60.5%
                |====================| 100.0%
                Transfer complete!
        */
    }
```

### 导入
<a name="transfer-manager-upload-imports"></a>

```
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import software.amazon.awssdk.transfer.s3.S3TransferManager;
import software.amazon.awssdk.transfer.s3.model.CompletedFileUpload;
import software.amazon.awssdk.transfer.s3.model.FileUpload;
import software.amazon.awssdk.transfer.s3.model.UploadFileRequest;
import software.amazon.awssdk.transfer.s3.progress.LoggingTransferListener;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Paths;
import java.util.UUID;
```

## 从 S3 桶下载文件
<a name="transfer-manager-download"></a>

以下示例显示了下载示例以及记录下载进度的可选用法。[LoggingTransferListener](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/transfer/s3/progress/LoggingTransferListener.html)

要使用 S3 传输管理器从 S3 存储桶下载对象，请生成一个[DownloadFileRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/transfer/s3/model/DownloadFileRequest.html)对象并将其传递给 [downloadFile](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/transfer/s3/S3TransferManager.html#downloadFile(software.amazon.awssdk.transfer.s3.model.DownloadFileRequest)) 方法。

的`downloadFile`方法返回`S3TransferManager`的[FileDownload](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/transfer/s3/model/FileDownload.html)对象表示文件传输。下载完成后，[CompletedFileDownload](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/transfer/s3/model/CompletedFileDownload.html)包含对下载相关信息的访问权限。

```
    public void trackDownloadFile(S3TransferManager transferManager, String bucketName,
                             String key, String downloadedFileWithPath) {
        DownloadFileRequest downloadFileRequest = DownloadFileRequest.builder()
                .getObjectRequest(b -> b.bucket(bucketName).key(key))
                .addTransferListener(LoggingTransferListener.create())  // Add listener.
                .destination(Paths.get(downloadedFileWithPath))
                .build();

        FileDownload downloadFile = transferManager.downloadFile(downloadFileRequest);

        CompletedFileDownload downloadResult = downloadFile.completionFuture().join();
        /*
            The SDK provides a LoggingTransferListener implementation of the TransferListener interface.
            You can also implement the interface to provide your own logic.

            Configure log4J2 with settings such as the following.
                <Configuration status="WARN">
                    <Appenders>
                        <Console name="AlignedConsoleAppender" target="SYSTEM_OUT">
                            <PatternLayout pattern="%m%n"/>
                        </Console>
                    </Appenders>

                    <Loggers>
                        <logger name="software.amazon.awssdk.transfer.s3.progress.LoggingTransferListener" level="INFO" additivity="false">
                            <AppenderRef ref="AlignedConsoleAppender"/>
                        </logger>
                    </Loggers>
                </Configuration>

            Log4J2 logs the progress. The following is example output for a 21.3 MB file download.
                Transfer initiated...
                |=======             | 39.4%
                |===============     | 78.8%
                |====================| 100.0%
                Transfer complete!
        */
    }
```

### 导入
<a name="transfer-manager-download-import"></a>

```
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import software.amazon.awssdk.core.exception.SdkException;
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.transfer.s3.S3TransferManager;
import software.amazon.awssdk.transfer.s3.model.CompletedFileDownload;
import software.amazon.awssdk.transfer.s3.model.DownloadFileRequest;
import software.amazon.awssdk.transfer.s3.model.FileDownload;
import software.amazon.awssdk.transfer.s3.progress.LoggingTransferListener;

import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.UUID;
```

## 将 Amazon S3 对象复制到另一个桶。
<a name="transfer-manager-copy"></a>

以下示例演示如何使用 S3 Transfer Manager 复制对象。

要开始将对象从 S3 存储桶复制到另一个存储桶，请创建一个基本[CopyObjectRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/s3/model/CopyObjectRequest.html)实例。

接下来，将基本内容封装`CopyObjectRequest`在 S3 传输管理器可以使用的文件中。[CopyRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/transfer/s3/model/CopyRequest.html)

`S3TransferManager` 的 `copy` 方法返回的 `Copy` 对象表示复制进程。复制过程完成后，该[CompletedCopy](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/transfer/s3/model/CompletedCopy.html)对象将包含有关响应的详细信息。

```
    public String copyObject(S3TransferManager transferManager, String bucketName,
            String key, String destinationBucket, String destinationKey) {
        CopyObjectRequest copyObjectRequest = CopyObjectRequest.builder()
                .sourceBucket(bucketName)
                .sourceKey(key)
                .destinationBucket(destinationBucket)
                .destinationKey(destinationKey)
                .build();

        CopyRequest copyRequest = CopyRequest.builder()
                .copyObjectRequest(copyObjectRequest)
                .build();

        Copy copy = transferManager.copy(copyRequest);

        CompletedCopy completedCopy = copy.completionFuture().join();
        return completedCopy.response().copyObjectResult().eTag();
    }
```

**注意**  
要使用 S3 传输管理器执行跨区域复制，请在 AWS 基于 CRT 的 S3 客户端生成器`crossRegionAccessEnabled`上启用，如以下代码段所示。  

```
S3AsyncClient s3AsyncClient = S3AsyncClient.crtBuilder()
                .crossRegionAccessEnabled(true)
                .build();

S3TransferManager transferManager = S3TransferManager.builder()
                .s3Client(s3AsyncClient)
                .build();
```

### 导入
<a name="transfer-manager-copy-import"></a>

```
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.services.s3.model.CopyObjectRequest;
import software.amazon.awssdk.transfer.s3.S3TransferManager;
import software.amazon.awssdk.transfer.s3.model.CompletedCopy;
import software.amazon.awssdk.transfer.s3.model.Copy;
import software.amazon.awssdk.transfer.s3.model.CopyRequest;

import java.util.UUID;
```

## 将本地目录上传到 S3 桶
<a name="transfer-manager-upload_directory"></a>

以下示例演示如何将本地目录上传到 S3。

首先调用`S3TransferManager`实例的 [uploadDirectory](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/transfer/s3/S3TransferManager.html#uploadDirectory(software.amazon.awssdk.transfer.s3.model.UploadDirectoryRequest)) 方法，传入。[UploadDirectoryRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/transfer/s3/model/UploadDirectoryRequest.html)

该[DirectoryUpload](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/transfer/s3/model/DirectoryUpload.html)对象表示上传过程，该过程会在请求完成[CompletedDirectoryUpload](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/transfer/s3/model/CompletedDirectoryUpload.html)时生成。`CompleteDirectoryUpload` 对象包含有关传输结果的信息，包括哪些文件传输失败。

```
    public Integer uploadDirectory(S3TransferManager transferManager,
            URI sourceDirectory, String bucketName) {
        DirectoryUpload directoryUpload = transferManager.uploadDirectory(UploadDirectoryRequest.builder()
                .source(Paths.get(sourceDirectory))
                .bucket(bucketName)
                .build());

        CompletedDirectoryUpload completedDirectoryUpload = directoryUpload.completionFuture().join();
        completedDirectoryUpload.failedTransfers()
                .forEach(fail -> logger.warn("Object [{}] failed to transfer", fail.toString()));
        return completedDirectoryUpload.failedTransfers().size();
    }
```

### 导入
<a name="transfer-manager-upload_directory-import"></a>

```
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import software.amazon.awssdk.services.s3.model.ObjectIdentifier;
import software.amazon.awssdk.transfer.s3.S3TransferManager;
import software.amazon.awssdk.transfer.s3.model.CompletedDirectoryUpload;
import software.amazon.awssdk.transfer.s3.model.DirectoryUpload;
import software.amazon.awssdk.transfer.s3.model.UploadDirectoryRequest;

import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Paths;
import java.util.UUID;
```

## 将 S3 桶对象下载到本地目录
<a name="transfer-manager-download_directory"></a>

您可以将 S3 桶中的对象下载到本地目录，如以下示例所示。

要将 S3 存储桶中的对象下载到本地目录，请首先调用传输管理器的 [downloadDirectory](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/transfer/s3/S3TransferManager.html#downloadDirectory(software.amazon.awssdk.transfer.s3.model.DownloadDirectoryRequest)) 方法，传入。[DownloadDirectoryRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/transfer/s3/model/DownloadDirectoryRequest.html)

该[DirectoryDownload](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/transfer/s3/model/DirectoryDownload.html)对象表示下载过程，该过程会在请求完成[CompletedDirectoryDownload](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/transfer/s3/model/CompletedDirectoryDownload.html)时生成。`CompleteDirectoryDownload` 对象包含有关传输结果的信息，包括哪些文件传输失败。

```
    public Integer downloadObjectsToDirectory(S3TransferManager transferManager,
            URI destinationPathURI, String bucketName) {
        DirectoryDownload directoryDownload = transferManager.downloadDirectory(DownloadDirectoryRequest.builder()
                .destination(Paths.get(destinationPathURI))
                .bucket(bucketName)
                .build());
        CompletedDirectoryDownload completedDirectoryDownload = directoryDownload.completionFuture().join();

        completedDirectoryDownload.failedTransfers()
                .forEach(fail -> logger.warn("Object [{}] failed to transfer", fail.toString()));
        return completedDirectoryDownload.failedTransfers().size();
    }
```

### 导入
<a name="transfer-manager-download_directory-import"></a>

```
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.services.s3.model.ObjectIdentifier;
import software.amazon.awssdk.transfer.s3.S3TransferManager;
import software.amazon.awssdk.transfer.s3.model.CompletedDirectoryDownload;
import software.amazon.awssdk.transfer.s3.model.DirectoryDownload;
import software.amazon.awssdk.transfer.s3.model.DownloadDirectoryRequest;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
```

## 查看完整示例
<a name="transfer-manager-example-location"></a>

[GitHub 包含本页上所有示例的完整](https://github.com/awsdocs/aws-doc-sdk-examples/tree/d73001daea05266eaa9e074ccb71b9383832369a/javav2/example_code/s3/src/main/java/com/example/s3/transfermanager)代码。

# 处理 S3 事件通知
<a name="examples-s3-event-notifications"></a>

为了帮助您监控存储桶中的活动，Amazon S3 可以在某些事件发生时发送通知。《Amazon S3 用户指南》提供了有关[存储桶可以发送的通知](https://docs.aws.amazon.com/AmazonS3/latest/userguide/EventNotifications.html#notification-how-to-overview)的信息。

您可以使用适用于 Java 的 SDK 设置存储桶，将事件发送到四个可能的目的地：
+ Amazon Simple Notification Service 主题
+ Amazon Simple Queue Service 队列
+ AWS Lambda 函数
+ Amazon EventBridge

当你设置要向其发送事件的存储桶时 EventBridge，你可以配置 EventBridge 一条规则，将同一个事件分散到多个目的地。将存储桶配置为直接发送到前三个目的地之一时，只能为每个事件指定一种目标类型。

在下一节中，您将了解如何使用适用于 Java 的 SDK 配置存储桶，以便通过两种方式发送 S3 事件通知：直接发送到 Amazon SQS 队列和发送到。 EventBridge

最后一部分说明了如何使用 S3 事件通知 API 来以面向对象的方式处理通知。

## 将存储桶配置为直接发送到目的地
<a name="s3-event-conf-bucket-direct"></a>

以下示例将存储桶配置为在针对存储桶发生*对象创建*事件或*对象标记*事件时发送通知。

```
static void processS3Events(String bucketName, String queueArn) {
    // Configure the bucket to send Object Created and Object Tagging notifications to an existing SQS queue.
    s3Client.putBucketNotificationConfiguration(b -> b
            .notificationConfiguration(ncb -> ncb
                    .queueConfigurations(qcb -> qcb
                            .events(Event.S3_OBJECT_CREATED, Event.S3_OBJECT_TAGGING)
                            .queueArn(queueArn)))
                    .bucket(bucketName)
    );
}
```

上面显示的代码设置了一个队列来接收两种类型的事件。方便的是，`queueConfigurations` 方法让您可以在需要时设置多个队列目的地。此外，在 `notificationConfiguration` 方法中，您可以设置其他目的地，例如一个或多个 Amazon SNS 主题或一个或多个 Lambda 函数。以下代码段展示的示例中包含两个队列和三种类型的目的地。

```
s3Client.putBucketNotificationConfiguration(b -> b
                .notificationConfiguration(ncb -> ncb
                        .queueConfigurations(qcb -> qcb
                                .events(Event.S3_OBJECT_CREATED, Event.S3_OBJECT_TAGGING)
                                .queueArn(queueArn), 
                                qcb2 -> qcb2.<...>)
                        .topicConfigurations(tcb -> tcb.<...>)
                        .lambdaFunctionConfigurations(lfcb -> lfcb.<...>))
                        .bucket(bucketName)
        );
```

代码示例 GitHub 存储库包含直接向队列发送 S3 事件通知的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/main/javav2/example_code/s3/src/main/java/com/example/s3/ProcessS3EventNotification.java)。

## 配置要发送到的存储桶 EventBridge
<a name="s3-event-conf-bucket-eventbridge"></a>

以下示例配置了要向其发送通知的存储桶。 EventBridge

```
public static String setBucketNotificationToEventBridge(String bucketName) {
    // Enable bucket to emit S3 Event notifications to EventBridge.
    s3Client.putBucketNotificationConfiguration(b -> b
            .bucket(bucketName)
            .notificationConfiguration(b1 -> b1
                    .eventBridgeConfiguration(SdkBuilder::build))
    .build());
```

配置要向其发送事件的存储桶时 EventBridge，您只需指明 EventBridge目的地，而不是事件的类型，也不是 EventBridge 要发送到的最终目的地。您可以使用 Java SDK 的 EventBridge客户端配置最终目标和事件类型。

以下代码显示了如何配置 EventBridge 以将*对象创建*的事件分散到主题和队列。

```
   public static String configureEventBridge(String topicArn, String queueArn) {
        try {
            // Create an EventBridge rule to route Object Created notifications.
            PutRuleRequest putRuleRequest = PutRuleRequest.builder()
                    .name(RULE_NAME)
                    .eventPattern("""
                            {
                              "source": ["aws.s3"],
                              "detail-type": ["Object Created"],
                              "detail": {
                                "bucket": {
                                  "name": ["%s"]
                                }
                              }
                            }
                            """.formatted(bucketName))
                    .build();

            // Add the rule to the default event bus.
            PutRuleResponse putRuleResponse = eventBridgeClient.putRule(putRuleRequest)
                    .whenComplete((r, t) -> {
                        if (t != null) {
                            logger.error("Error creating event bus rule: " + t.getMessage(), t);
                            throw new RuntimeException(t.getCause().getMessage(), t);
                        }
                        logger.info("Event bus rule creation request sent successfully. ARN is: {}", r.ruleArn());
                    }).join();

            // Add the existing SNS topic and SQS queue as targets to the rule.
            eventBridgeClient.putTargets(b -> b
                    .eventBusName("default")
                    .rule(RULE_NAME)
                    .targets(List.of (
                            Target.builder()
                                    .arn(queueArn)
                                    .id("Queue")
                                    .build(),
                            Target.builder()
                                    .arn(topicArn)
                                    .id("Topic")
                                    .build())
                            )
                    ).join();
            return putRuleResponse.ruleArn();
        } catch (S3Exception e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
        return null;
    }
```

要在 Java 代码 EventBridge 中使用，请在 Maven `pom.xml` 文件中添加对`eventbridge`工件的依赖关系。

```
<dependency>
    <groupId>software.amazon.awssdk</groupId>
    <artifactId>eventbridge</artifactId>
</dependency>
```

代码示例 GitHub 存储库包含向主题 EventBridge 和队列发送 S3 事件通知的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/main/javav2/example_code/s3/src/main/java/com/example/s3/PutBucketS3EventNotificationEventBridge.java)，然后向主题和队列发送。

## 使用 S3 事件通知 API 来处理事件
<a name="s3-event-notification-read"></a>

目的地收到 S3 通知事件后，您可以使用 S3 事件通知 API 以面向对象的方式处理这些事件。您可以使用 S3 事件通知 API 来处理直接发送到目标的事件通知（如[第一个示例](#s3-event-conf-bucket-direct)所示），但不能处理通过 EventBridge发送的通知。存储桶发送的 S3 事件通知 EventBridge 包含 S3 事件通知 API 当前无法处理的[不同结构](https://docs.aws.amazon.com/AmazonS3/latest/userguide/ev-events.html#ev-events-list)。

### 添加依赖项
<a name="s3-event-notifications-dep"></a>

S3 事件通知 API 随适用于 Java 的 SDK 2.x 的 2.25.11 版本一起发布。

要使用 S3 事件通知 API，请将所需的依赖项元素添加到您的 Maven `pom.xml` 中，如以下代码段所示。

```
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>software.amazon.awssdk</groupId>
            <artifactId>bom</artifactId>
            <version>2.X.X1</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
<dependencies>
    <dependency>
        <groupId>software.amazon.awssdk</groupId>
        <artifactId>s3-event-notifications</artifactId>
    </dependency>
</dependencies>
```

1 [最新版本](https://central.sonatype.com/artifact/software.amazon.awssdk/bom)。

### 使用 `S3EventNotification` 类
<a name="s3-event-notifications-use"></a>

#### 使用 JSON 字符串创建 `S3EventNotification` 实例
<a name="s3-event-notifications-use-from-json"></a>

要将 JSON 字符串转换为 `S3EventNotification` 对象，请使用 `S3EventNotification` 类的静态方法，如下例所示。

```
import software.amazon.awssdk.eventnotifications.s3.model.S3EventNotification
import software.amazon.awssdk.eventnotifications.s3.model.S3EventNotificationRecord
import software.amazon.awssdk.services.sqs.model.Message; 

public class S3EventNotificationExample {
    ...
    
    void receiveMessage(Message message) {
       // Message received from SQSClient.
       String sqsEventBody = message.body();
       S3EventNotification s3EventNotification = S3EventNotification.fromJson(sqsEventBody);
    
       // Use getRecords() to access all the records in the notification.                                                                                                       
       List<S3EventNotificationRecord> records = s3EventNotification.getRecords();   
    
        S3EventNotificationRecord record = records.stream().findFirst();
        // Use getters on the record to access individual attributes.
        String awsRegion = record.getAwsRegion();
        String eventName = record.getEventName();
        String eventSource = record.getEventSource();                                                                                                   
    }
}
```

在此示例中，`fromJson` 方法将 JSON 字符串转换为 `S3EventNotification` 对象。JSON 字符串中缺少字段会导致相应 Java 对象字段设置为 `null` 值，而 JSON 中的任何多余字段则会被忽略。

事件通知记录的其他 APIs 内容可在的 API 参考中找到`[S3EventNotificationRecord](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/eventnotifications/s3/model/S3EventNotificationRecord.html)`。

#### 将 `S3EventNotification` 实例转换为 JSON 字符串
<a name="s3-event-notifications-use-to-json"></a>

使用 `toJson`（或 `toJsonPretty`）方法将 `S3EventNotification` 对象转换为 JSON 字符串，如下例所示。

```
import software.amazon.awssdk.eventnotifications.s3.model.S3EventNotification

public class S3EventNotificationExample {
    ...

    void toJsonString(S3EventNotification event) {

        String json = event.toJson();
        String jsonPretty = event.toJsonPretty();

        System.out.println("JSON: " + json);
        System.out.println("Pretty JSON: " + jsonPretty);
    }
}
```

`GlacierEventData`、`ReplicationEventData`、`IntelligentTieringEventData` 和 `LifecycleEventData` 的字段如果为 `null`，则会从 JSON 中排除。其他 `null` 字段将序列化为 `null`。

下面显示了 S3 对象标记事件的 `toJsonPretty` 方法示例输出。

```
{
  "Records" : [ {
    "eventVersion" : "2.3",
    "eventSource" : "aws:s3",
    "awsRegion" : "us-east-1",
    "eventTime" : "2024-07-19T20:09:18.551Z",
    "eventName" : "ObjectTagging:Put",
    "userIdentity" : {
      "principalId" : "AWS:XXXXXXXXXXX"
    },
    "requestParameters" : {
      "sourceIPAddress" : "XXX.XX.XX.XX"
    },
    "responseElements" : {
      "x-amz-request-id" : "XXXXXXXXXXXX",
      "x-amz-id-2" : "XXXXXXXXXXXXX"
    },
    "s3" : {
      "s3SchemaVersion" : "1.0",
      "configurationId" : "XXXXXXXXXXXXX",
      "bucket" : {
        "name" : "amzn-s3-demo-bucket",
        "ownerIdentity" : {
          "principalId" : "XXXXXXXXXXX"
        },
        "arn" : "arn:aws:s3:::XXXXXXXXXX"
      },
      "object" : {
        "key" : "akey",
        "size" : null,
        "eTag" : "XXXXXXXXXX",
        "versionId" : null,
        "sequencer" : null
      }
    }
  } ]
}
```

中提供了一个[完整的示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/75c3daadf750406156fc87fa30ee499a206b4a36/javav2/example_code/s3/src/main/java/com/example/s3/ProcessS3EventNotification.java#L117) GitHub ，其中显示了如何使用 API 来处理 Amazon SQS 队列收到的通知。

## 使用 Java 库在 Lambda 中处理 S3 事件：以及 AWS SDK for Java 2.x `aws-lambda-java-events`
<a name="s3-event-notif-processing-options"></a>

您可以使用 3.x.x 版本的库，而不是使用`[aws-lambda-java-events](https://github.com/aws/aws-lambda-java-libs/tree/main/aws-lambda-java-events)`适用于 Java 2.x 的 SDK 在 Lambda 函数中处理 Amazon S3 事件通知。 AWS 独立维护该`aws-lambda-java-events`库，并且它有自己的依赖要求。`aws-lambda-java-events` 库仅适用于 Lambda 函数中的 S3 事件，而适用于 Java 的 SDK 2.x 可处理 Lambda 函数、Amazon SNS 和 Amazon SQS 中的 S3 事件。

两种方法都以类似的面向对象的方式建模 JSON 事件通知有效负载。 APIs下表显示了使用这两种方法的显著差异。


****  

|  | 适用于 Java 的 AWS SDK | aws-lambda-java-events 图书馆 | 
| --- | --- | --- | 
| 软件包命名 |  `software.amazon.awssdk.eventnotifications.s3.model.S3EventNotification`  | com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification | 
| RequestHandler 参数 |  编写您的 Lambda 函数的 `RequestHandler` 实现以接收 JSON 字符串： <pre>import com.amazonaws.services.lambda.runtime.Context;<br />import com.amazonaws.services.lambda.runtime.RequestHandler;<br />import software.amazon.awssdk.eventnotifications.s3.model.S3EventNotification;<br /><br />public class Handler implements RequestHandler<String, String> {<br /><br />    @Override<br />        public String handleRequest(String jsonS3Event, Context context) {<br />            S3EventNotification s3Event = S3EventNotification<br />                                             .fromJson(jsonS3Event);<br />            // Work with the s3Event object.        <br />            ...<br />    }<br />}</pre>  | 编写您的 Lambda 函数的 RequestHandler 实现以接收 S3Event 对象：<pre>import com.amazonaws.services.lambda.runtime.Context;<br />import com.amazonaws.services.lambda.runtime.RequestHandler;<br />import com.amazonaws.services.lambda.runtime.events.S3Event;<br /><br />public class Handler implements RequestHandler<S3Event, String> {<br /><br />    @Override<br />        public String handleRequest(S3Event s3event, Context context) {<br />            // Work with the s3Event object.        <br />            ...<br />    }<br />}</pre> | 
| Maven 依赖项 |  <pre><dependencyManagement><br />    <dependencies><br />        <dependency><br />            <groupId>software.amazon.awssdk</groupId><br />            <artifactId>bom</artifactId><br />            <version>2.X.X</version><br />            <type>pom</type><br />            <scope>import</scope><br />        </dependency><br />    </dependencies><br /></dependencyManagement><br /><dependencies><br />    <dependency><br />        <groupId>software.amazon.awssdk</groupId><br />        <artifactId>s3-event-notifications</artifactId><br />    </dependency><br />    <!-- Add other SDK dependencies that you need. --><br /></dependencies></pre>  |  <pre><dependencyManagement><br />    <dependencies><br />        <dependency><br />            <groupId>software.amazon.awssdk</groupId><br />            <artifactId>bom</artifactId><br />            <version>2.X.X</version><br />            <type>pom</type><br />            <scope>import</scope><br />        </dependency><br />    </dependencies><br /></dependencyManagement><br /><dependencies><br />    <!-- The following two dependencies are for the <br />         aws-lambda-java-events library. --><br />    <dependency><br />        <groupId>com.amazonaws</groupId><br />        <artifactId>aws-lambda-java-core</artifactId><br />        <version>1.2.3</version>     <br />    </dependency><br />    <dependency><br />        <groupId>com.amazonaws</groupId><br />        <artifactId>aws-lambda-java-events</artifactId><br />        <version>3.15.0</version><br />    </dependency><br />    <!-- Add other SDK dependencies that you need. --><br /></dependencies></pre>  | 

# 使用 Amazon Simple Notification Service
<a name="examples-simple-notification-service"></a>

利用 Amazon Simple Notification Service，您可以通过多个通信通道轻松地将实时通知消息从您的应用程序推送给订阅者。本主题介绍如何执行 Amazon SNS 的一些基本功能。

## 创建主题
<a name="sns-create-topic"></a>

**主题**是通信通道的逻辑分组，可定义要将消息发送到的系统，例如，将消息发送到 AWS Lambda 和 HTTP Webhook。将消息发送到 Amazon SNS 之后，这些消息将分发到主题中定义的各个通道。这将使订阅者能够收到这些消息。

要创建主题，请先构建一个 [CreateTopicRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sns/model/CreateTopicRequest.html) 对象，并使用构建器中的 `name()` 方法设置主题的名称。然后，使用 [SnsClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sns/SnsClient.html) 的 `createTopic()` 方法将请求对象发送到 Amazon SNS。可以将此请求的结果作为 [CreateTopicResponse](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sns/model/CreateTopicResponse.html) 对象捕获，如以下代码段中所示。

 **导入**。

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.sns.SnsClient;
import software.amazon.awssdk.services.sns.model.CreateTopicRequest;
import software.amazon.awssdk.services.sns.model.CreateTopicResponse;
import software.amazon.awssdk.services.sns.model.SnsException;
```

 **代码** 

```
    public static String createSNSTopic(SnsClient snsClient, String topicName ) {

        CreateTopicResponse result = null;
        try {
            CreateTopicRequest request = CreateTopicRequest.builder()
                    .name(topicName)
                    .build();

            result = snsClient.createTopic(request);
            return result.topicArn();
        } catch (SnsException e) {

            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
        return "";
    }
```

请参阅 GitHub 上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/da520cb4436f8567a90b6f73f77232fd590a50bf/javav2/example_code/sns/src/main/java/com/example/sns/CreateTopic.java)。

## 列出 Amazon SNS 主题
<a name="sns-crelistate-topics"></a>

要检索现有 Amazon SNS 主题的列表，请构建一个 [ListTopicsRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sns/model/ListTopicsRequest.html) 对象。然后，使用 Amazon SNS 的 `listTopics()` 方法将请求对象发送到 `SnsClient`。可以将此请求的结果作为 [ListTopicsResponse](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sns/model/ListTopicsResponse.html) 对象捕获。

以下代码段输出请求的 HTTP 状态代码以及您的 Amazon SNS 主题的 Amazon 资源名称 (ARN) 列表。

 **导入**。

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.sns.SnsClient;
import software.amazon.awssdk.services.sns.model.ListTopicsRequest;
import software.amazon.awssdk.services.sns.model.ListTopicsResponse;
import software.amazon.awssdk.services.sns.model.SnsException;
```

 **代码** 

```
    public static void listSNSTopics(SnsClient snsClient) {

        try {
            ListTopicsRequest request = ListTopicsRequest.builder()
                   .build();

            ListTopicsResponse result = snsClient.listTopics(request);
            System.out.println("Status was " + result.sdkHttpResponse().statusCode() + "\n\nTopics\n\n" + result.topics());

        } catch (SnsException e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
    }
```

请参阅 GitHub 上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/da520cb4436f8567a90b6f73f77232fd590a50bf/javav2/example_code/sns/src/main/java/com/example/sns/ListTopics.java)。

## 为终端节点订阅主题
<a name="sns-subscribe-endpoint-topic"></a>

创建主题后，您可以配置将哪些通信通道作为该主题的终端节点。在 Amazon SNS 收到消息后，消息将分发给这些终端节点。

要将通信通道配置为主题的终端节点，请为该终端节点订阅主题。首先，请构建一个 [SubscribeRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sns/model/SubscribeRequest.html) 对象。将通信通道（例如，`lambda` 或 `email`）指定为 `protocol()`。将 `endpoint()` 设置为相关输出位置（例如，Lambda 函数的 ARN 或电子邮件地址），然后将要订阅的主题的 ARN 设置为 `topicArn()`。使用 `SnsClient` 的 `subscribe()` 方法将请求对象发送到 Amazon SNS。可以将此请求的结果作为 [SubscribeResponse](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sns/model/SubscribeResponse.html) 对象捕获。

以下代码段说明如何为电子邮件地址订阅主题。

 **导入**。

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.sns.SnsClient;
import software.amazon.awssdk.services.sns.model.SnsException;
import software.amazon.awssdk.services.sns.model.SubscribeRequest;
import software.amazon.awssdk.services.sns.model.SubscribeResponse;
```

 **代码** 

```
    public static void subEmail(SnsClient snsClient, String topicArn, String email) {

        try {
            SubscribeRequest request = SubscribeRequest.builder()
                .protocol("email")
                .endpoint(email)
                .returnSubscriptionArn(true)
                .topicArn(topicArn)
                .build();

            SubscribeResponse result = snsClient.subscribe(request);
            System.out.println("Subscription ARN: " + result.subscriptionArn() + "\n\n Status is " + result.sdkHttpResponse().statusCode());

        } catch (SnsException e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
    }
```

请参阅 GitHub 上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/da520cb4436f8567a90b6f73f77232fd590a50bf/javav2/example_code/sns/src/main/java/com/example/sns/SubscribeEmail.java)。

## 向主题发布消息
<a name="sns-publish-message-topic"></a>

如果您拥有一个主题并且已为该主题配置一个或多个终端节点，则可向该主题发布消息。首先，请构建一个 [PublishRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sns/model/PublishRequest.html) 对象。指定要发送的 `message()`，并指定要将消息发送到的主题的 ARN (`topicArn()`)。然后，使用 Amazon SNS 的 `publish()` 方法将请求对象发送到 `SnsClient`。可以将此请求的结果作为 [PublishResponse](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sns/model/PublishResponse.html) 对象捕获。

 **导入**。

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.sns.SnsClient;
import software.amazon.awssdk.services.sns.model.PublishRequest;
import software.amazon.awssdk.services.sns.model.PublishResponse;
import software.amazon.awssdk.services.sns.model.SnsException;
```

 **代码** 

```
    public static void pubTopic(SnsClient snsClient, String message, String topicArn) {

        try {
            PublishRequest request = PublishRequest.builder()
                .message(message)
                .topicArn(topicArn)
                .build();

            PublishResponse result = snsClient.publish(request);
            System.out.println(result.messageId() + " Message sent. Status is " + result.sdkHttpResponse().statusCode());

         } catch (SnsException e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
         }
    }
```

请参阅 GitHub 上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/da520cb4436f8567a90b6f73f77232fd590a50bf/javav2/example_code/sns/src/main/java/com/example/sns/PublishTopic.java)。

## 为终端节点取消订阅主题
<a name="sns-unsubscribe-endpoint-topic"></a>

可以删除配置为主题的终端节点的通信通道。执行此操作后，主题本身将继续存在，并会将消息分发到为该主题配置的任何其他终端节点。

要删除作为主题的终端节点的通信通道，请为该终端节点取消订阅主题。首先，请构建一个 [UnsubscribeRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sns/model/UnsubscribeRequest.html) 对象，并将要取消订阅的主题的 ARN 设置为 `subscriptionArn()`。然后，使用 `unsubscribe()` 的 `SnsClient` 方法将请求对象发送到 SNS。可以将此请求的结果作为 [UnsubscribeResponse](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sns/model/UnsubscribeResponse.html) 对象捕获。

 **导入**。

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.sns.SnsClient;
import software.amazon.awssdk.services.sns.model.SnsException;
import software.amazon.awssdk.services.sns.model.UnsubscribeRequest;
import software.amazon.awssdk.services.sns.model.UnsubscribeResponse;
```

 **代码** 

```
    public static void unSub(SnsClient snsClient, String subscriptionArn) {

        try {
            UnsubscribeRequest request = UnsubscribeRequest.builder()
                .subscriptionArn(subscriptionArn)
                .build();

            UnsubscribeResponse result = snsClient.unsubscribe(request);

            System.out.println("\n\nStatus was " + result.sdkHttpResponse().statusCode()
                + "\n\nSubscription was removed for " + request.subscriptionArn());

        } catch (SnsException e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
    }
```

请参阅 GitHub 上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/da520cb4436f8567a90b6f73f77232fd590a50bf/javav2/example_code/sns/src/main/java/com/example/sns/Unsubscribe.java)。

## 删除主题
<a name="sns-delete-topic"></a>

要删除 Amazon SNS 主题，请先构建一个 [DeleteTopicRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sns/model/DeleteTopicRequest.html) 对象，并将主题的 ARN 设置为构建器中的 `topicArn()` 方法。然后，使用 Amazon SNS 的 `deleteTopic()` 方法将请求对象发送到 `SnsClient`。可以将此请求的结果作为 [DeleteTopicResponse](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sns/model/DeleteTopicResponse.html) 对象捕获，如以下代码段中所示。

 **导入**。

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.sns.SnsClient;
import software.amazon.awssdk.services.sns.model.DeleteTopicRequest;
import software.amazon.awssdk.services.sns.model.DeleteTopicResponse;
import software.amazon.awssdk.services.sns.model.SnsException;
```

 **代码** 

```
    public static void deleteSNSTopic(SnsClient snsClient, String topicArn ) {

        try {
            DeleteTopicRequest request = DeleteTopicRequest.builder()
                .topicArn(topicArn)
                .build();

            DeleteTopicResponse result = snsClient.deleteTopic(request);
            System.out.println("\n\nStatus was " + result.sdkHttpResponse().statusCode());

        } catch (SnsException e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
    }
```

请参阅 GitHub 上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/da520cb4436f8567a90b6f73f77232fd590a50bf/javav2/example_code/sns/src/main/java/com/example/sns/DeleteTopic.java)。

有关更多信息，请参见[Amazon Simple Notification Service 开发人员指南](https://docs.aws.amazon.com/sns/latest/dg/)。

# 使用 Amazon Simple Queue Service
<a name="examples-sqs"></a>

本部分提供使用适用于 Java 的 AWS SDK 2.x 对 [Amazon Simple Queue Service](https://docs.aws.amazon.com/sqs/) 进行编程的示例。

以下示例仅包含演示每种方法所需的代码。[完整的示例代码在 GitHub 上提供](https://github.com/awsdocs/aws-doc-sdk-examples/tree/main/javav2)。您可以从中下载单个源文件，也可以将存储库复制到本地以获得所有示例，然后构建并运行它们。

**Topics**
+ [使用自动请求批处理](sqs-auto-batch.md)
+ [队列操作](examples-sqs-message-queues.md)
+ [消息操作](examples-sqs-messages.md)

# 将 Amazon SQS 的自动请求批处理与 AWS SDK for Java 2.x
<a name="sqs-auto-batch"></a>

Amazon SQS 的自动请求批处理 API 是一个高级库，提供了一种高效的方法来批处理和缓冲 SQS 操作的请求。通过使用批处理 API，您可以减少 SQS 的请求数量，从而提高吞吐量并更大限度地降低成本。

由于批处理 API 方法与 `[SqsAsyncClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sqs/SqsAsyncClient.html)` 方法（`sendMessage`、`changeMessageVisibility`、`deleteMessage`、`receiveMessage`）相匹配，因此您可以将批处理 API 作为直接替代方案使用，只需做极少的代码修改。

本主题概述了如何配置和使用 Amazon SQS 的自动请求批处理 API。

## 检查先决条件
<a name="sqs-auto-batch-requirements"></a>

您需要使用适用于 Java 的 SDK 2.x 的版本 *2.28.0* 或更高版本才能访问批处理 API。您的 Maven `pom.xml` 应至少包含以下元素。

```
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>software.amazon.awssdk</groupId>
            <artifactId>bom</artifactId>
            <version>2.28.231</version>
            <type>pom</type>
            <scope>import</scope>
        </dependency>
    </dependencies>
</dependencyManagement>
<dependencies>
    <dependency>
        <groupId>software.amazon.awssdk</groupId>
        <artifactId>sqs</artifactId>
    </dependency>
</dependencies>
```

1 [最新版本](https://central.sonatype.com/artifact/software.amazon.awssdk/bom)

## 创建批量管理器
<a name="sqs-auto-batch-create"></a>

自动请求批处理 API 由[SqsAsyncBatchManager](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sqs/batchmanager/SqsAsyncBatchManager.html)接口实现。您可以通过几种方式创建管理器的实例。

### 使用 `SqsAsyncClient` 的默认配置
<a name="sqs-batch-manager-create-default"></a>

创建批处理管理器的最简单方法是在现有[SqsAsyncClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sqs/SqsAsyncClient.html)实例上调用`batchManager`工厂方法。以下代码段演示了这种简单方法。

```
SqsAsyncClient asyncClient = SqsAsyncClient.create();
SqsAsyncBatchManager sqsAsyncBatchManager = asyncClient.batchManager();
```

当您使用这种方法时，`SqsAsyncBatchManager` 实例会使用 [覆盖 `SqsAsyncBatchManager` 的配置设置](#sqs-auto-batch-config-settings) 部分表格中显示的默认值。此外，`SqsAsyncBatchManager` 实例会使用其创建来源的 `SqsAsyncClient` 实例的 `ExecutorService`。

### 使用 `SqsAsyncBatchManager.Builder` 的自定义配置
<a name="sqs-batch-manager-create-custom"></a>

对于更高级的使用案例，您可以使用 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sqs/batchmanager/SqsAsyncBatchManager.Builder.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sqs/batchmanager/SqsAsyncBatchManager.Builder.html) 自定义批处理管理器。通过使用这种方法来创建 `SqsAsyncBatchManager` 实例，您可以微调批处理行为。以下代码段举例说明了如何使用生成器来自定义批处理行为。

```
SqsAsyncBatchManager batchManager = SqsAsyncBatchManager.builder()
    .client(SqsAsyncClient.create())
    .scheduledExecutor(Executors.newScheduledThreadPool(5))
    .overrideConfiguration(b -> b
        .receiveMessageMinWaitDuration(Duration.ofSeconds(10))
        .receiveMessageVisibilityTimeout(Duration.ofSeconds(1))
        .receiveMessageAttributeNames(Collections.singletonList("*"))
        .receiveMessageSystemAttributeNames(Collections.singletonList(MessageSystemAttributeName.ALL)))
    .build();
```

使用这种方法时，您可以调整 [覆盖 `SqsAsyncBatchManager` 的配置设置](#sqs-auto-batch-config-settings) 部分表格中显示的 `BatchOverrideConfiguration` 对象的设置。您也可以使用这种方法为批处理管理器提供自定义 [https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/concurrent/ScheduledExecutorService.html](https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/util/concurrent/ScheduledExecutorService.html)。

## 发送消息
<a name="sqs-auto-batch-send"></a>

要使用批处理管理器发送消息，请使用 `[SqsAsyncBatchManager\$1sendMessage](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sqs/batchmanager/SqsAsyncBatchManager.html#sendMessage(software.amazon.awssdk.services.sqs.model.SendMessageRequest))` 方法。SDK 会缓冲请求，并在达到 `maxBatchSize` 或 `sendRequestFrequency` 值时批量发送请求。

以下示例展示了一个 `sendMessage` 请求后立即紧跟着另一个请求。在这种情况下，SDK 在单个批次中发送这两条消息。

```
// Sending the first message
CompletableFuture<SendMessageResponse> futureOne = 
    sqsAsyncBatchManager.sendMessage(r -> r.messageBody("One").queueUrl("queue"));

// Sending the second message
CompletableFuture<SendMessageResponse> futureTwo = 
    sqsAsyncBatchManager.sendMessage(r -> r.messageBody("Two").queueUrl("queue"));

// Waiting for both futures to complete and retrieving the responses
SendMessageResponse messageOne = futureOne.join();
SendMessageResponse messageTwo = futureTwo.join();
```

## 更改消息的可见性超时
<a name="sqs-auto-batch-change-vis"></a>

您可以使用 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sqs/batchmanager/SqsAsyncBatchManager.html#changeMessageVisibility(java.util.function.Consumer)](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sqs/batchmanager/SqsAsyncBatchManager.html#changeMessageVisibility(java.util.function.Consumer)) 方法批量更改消息的可见性超时。SDK 会缓冲请求，并在达到 `maxBatchSize` 或 `sendRequestFrequency` 值时批量发送请求。

以下示例显示了如何调用 `changeMessageVisibility` 方法。

```
CompletableFuture<ChangeMessageVisibilityResponse> futureOne =
    sqsAsyncBatchManager.changeMessageVisibility(r -> 
        r.receiptHandle("receiptHandle")
         .queueUrl("queue"));
ChangeMessageVisibilityResponse response = futureOne.join();
```

## 删除消息
<a name="sqs-auto-batch-delete"></a>

您可以使用 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sqs/batchmanager/SqsAsyncBatchManager.html#deleteMessage(java.util.function.Consumer)](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sqs/batchmanager/SqsAsyncBatchManager.html#deleteMessage(java.util.function.Consumer)) 方法批量删除消息。SDK 会缓冲请求，并在达到 `maxBatchSize` 或 `sendRequestFrequency` 值时批量发送请求。

以下示例说明了如何调用 `deleteMessage` 方法。

```
CompletableFuture<DeleteMessageResponse> futureOne = 
    sqsAsyncBatchManager.deleteMessage(r -> 
        r.receiptHandle("receiptHandle")
         .queueUrl("queue"));
DeleteMessageResponse response = futureOne.join();
```

## 接收消息
<a name="sqs-auto-batch-receive"></a>

### 使用默认设置
<a name="sqs-auto-batch-receive-default-settings"></a>

当您在应用程序中轮询 `[SqsAsyncBatchManager\$1receiveMessage](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sqs/batchmanager/SqsAsyncBatchManager.html#receiveMessage(java.util.function.Consumer))` 方法时，批处理管理器会从其内部缓冲区获取消息，SDK 会在后台自动更新缓冲区。

以下示例显示了如何调用 `receiveMessage` 方法。

```
CompletableFuture<ReceiveMessageResponse> responseFuture = 
    sqsAsyncBatchManager.receiveMessage(r -> r.queueUrl("queueUrl"));
```

### 使用自定义设置
<a name="sqs-auto-batch-receive-custom-settings"></a>

如果您想进一步自定义请求（例如设置自定义等待时间和指定要检索的消息数量），则可以按以下示例所示自定义请求。

```
CompletableFuture<ReceiveMessageResponse> response = 
    sqsAsyncBatchManager.receiveMessage(r -> 
        r.queueUrl("queueUrl")
         .waitTimeSeconds(5)
         .visibilityTimeout(20));
```

**注意**  
如果您使用包括以下任何参数的 `[ReceiveMessageRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sqs/model/ReceiveMessageRequest.html)` 来调用 `receiveMessage`，则 SDK 会绕过批处理管理器并发送常规异步 `receiveMessage` 请求：  
`messageAttributeNames`
`messageSystemAttributeNames`
`messageSystemAttributeNamesWithStrings`
`overrideConfiguration`

## 覆盖 `SqsAsyncBatchManager` 的配置设置
<a name="sqs-auto-batch-config-settings"></a>

创建 `SqsAsyncBatchManager` 实例时，可以调整以下设置。[https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sqs/batchmanager/BatchOverrideConfiguration.Builder.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sqs/batchmanager/BatchOverrideConfiguration.Builder.html) 提供了以下设置列表。


| 设置 | 说明 | 默认 值 | 
| --- | --- | --- | 
| maxBatchSize | 每个 SendMessageBatchRequest、ChangeMessageVisibilityBatchRequest 或 DeleteMessageBatchRequest 每批次的最大请求数。最大值为 10。 | 10 | 
| sendRequestFrequency |  发送批次之前的时间，除非提前达到 `maxBatchSize`。较高的值可能会减少请求，但会增加延迟。  | 200 毫秒 | 
| receiveMessageVisibilityTimeout | 消息的可见性超时。如果未设置，则使用队列的默认值。 | 队列的默认值 | 
| receiveMessageMinWaitDuration | receiveMessage 请求的最短等待时间。避免设置为 0，以防浪费 CPU。 | 50 毫秒 | 
| receiveMessageSystemAttributeNames | receiveMessage 调用请求的[系统属性名称](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sqs/model/MessageSystemAttributeName.html)列表。 | 无 | 
| receiveMessageAttributeNames | receiveMessage 调用请求的[属性名称](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-message-metadata.html#sqs-message-attributes)列表。 | 无 | 

# 处理 Amazon Simple Queue Service 消息队列
<a name="examples-sqs-message-queues"></a>

*消息队列*是用于在中可靠地发送消息的逻辑容器 Amazon Simple Queue Service。有两种类型的队列：*标准* 和*先进先出* (FIFO)。要了解有关队列以及这些类型之间的差异的更多信息，请参阅《[Amazon Simple Queue Service Developer Guide](https://docs.aws.amazon.com//AWSSimpleQueueService/latest/SQSDeveloperGuide/welcome.html)》。

本主题介绍如何使用创建、列出、删除队列和获取 Amazon Simple Queue Service 队列的 URL 适用于 Java 的 AWS SDK。

以下示例中使用的 `sqsClient` 变量可以通过以下代码段创建。

```
SqsClient sqsClient = SqsClient.create();
```

使用静态 `create()` 方法创建 `SqsClient` 时，SDK 使用[默认区域提供程序链](region-selection.md#default-region-provider-chain)配置区域，使用[默认凭证提供程序链](credentials-chain.md)配置凭证。

## 创建队列
<a name="sqs-create-queue"></a>

使用 `SqsClient’s` `createQueue` 方法，并提供一个描述队列参数的 `[CreateQueueRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sqs/model/CreateQueueRequest.html)` 对象，如以下代码段所示。

 **导入** 

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.sqs.SqsClient;
import software.amazon.awssdk.services.sqs.model.*;
import java.util.List;
```

 **代码** 

```
            CreateQueueRequest createQueueRequest = CreateQueueRequest.builder()
                .queueName(queueName)
                .build();

            sqsClient.createQueue(createQueueRequest);
```

请查看上[面的完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/cf25559da654a7b74bec039c0ab9397dc5951dd4/javav2/example_code/sqs/src/main/java/com/example/sqs/SQSExample.java#L52) GitHub。

## 列出队列
<a name="sqs-list-queues"></a>

要列出您账户的 Amazon Simple Queue Service 队列，请使用`[ListQueuesRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sqs/model/ListQueuesRequest.html)`对象调用`SqsClient’s``listQueues`方法。

当您使用不带任何参数的 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sqs/SqsClient.html#listQueues()](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sqs/SqsClient.html#listQueues()) 方法形式时，服务会返回*所有队列*（最多 1000 个）。

您可以向 `[ListQueuesRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sqs/model/ListQueuesRequest.html)` 对象提供一个队列名称前缀，以将结果限制为与该前缀匹配的队列，如以下代码所示。

 **导入** 

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.sqs.SqsClient;
import software.amazon.awssdk.services.sqs.model.*;
import java.util.List;
```

 **代码** 

```
        String prefix = "que";

        try {
            ListQueuesRequest listQueuesRequest = ListQueuesRequest.builder().queueNamePrefix(prefix).build();
            ListQueuesResponse listQueuesResponse = sqsClient.listQueues(listQueuesRequest);

            for (String url : listQueuesResponse.queueUrls()) {
                System.out.println(url);
            }

        } catch (SqsException e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
```

请查看上[面的完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/cf25559da654a7b74bec039c0ab9397dc5951dd4/javav2/example_code/sqs/src/main/java/com/example/sqs/SQSExample.java#L79) GitHub。

## 获取队列的 URL
<a name="sqs-get-queue-url"></a>

以下代码显示如何通过调用带有 `[GetQueueUrlRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sqs/model/GetQueueUrlRequest.html)` 对象的 `SqsClient’s` `getQueueUrl` 方法来获取队列的 URL。

 **导入** 

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.sqs.SqsClient;
import software.amazon.awssdk.services.sqs.model.*;
import java.util.List;
```

 **代码** 

```
            GetQueueUrlResponse getQueueUrlResponse =
                sqsClient.getQueueUrl(GetQueueUrlRequest.builder().queueName(queueName).build());
            String queueUrl = getQueueUrlResponse.queueUrl();
            return queueUrl;
```

请查看上[面的完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/7486a1a092aa8e16a21698ef26f9d524fef62e55/javav2/example_code/sqs/src/main/java/com/example/sqs/SQSExample.java#L70) GitHub。

## 删除队列
<a name="sqs-delete-queue"></a>

提供队列中指向 `[DeleteQueueRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sqs/model/DeleteQueueRequest.html)` 对象的 [URL](#sqs-get-queue-url)。然后调用 `SqsClient’s` `deleteQueue` 方法来删除队列，如以下代码所示。

 **导入** 

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.sqs.SqsClient;
import software.amazon.awssdk.services.sqs.model.*;
import java.util.List;
```

 **代码** 

```
    public static void deleteSQSQueue(SqsClient sqsClient, String queueName) {

        try {

            GetQueueUrlRequest getQueueRequest = GetQueueUrlRequest.builder()
                    .queueName(queueName)
                    .build();

            String queueUrl = sqsClient.getQueueUrl(getQueueRequest).queueUrl();

            DeleteQueueRequest deleteQueueRequest = DeleteQueueRequest.builder()
                    .queueUrl(queueUrl)
                    .build();

            sqsClient.deleteQueue(deleteQueueRequest);

        } catch (SqsException e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
    }
```

请查看上[面的完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/6240df86c5f17eae1e23d1139d1435c7dc4b2a11/javav2/example_code/sqs/src/main/java/com/example/sqs/DeleteQueue.java#L48) GitHub。

## 更多信息
<a name="more-information"></a>
+  [CreateQueue](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_CreateQueue.html)在 Amazon Simple Queue Service API 参考中
+  [GetQueueUrl](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_GetQueueUrl.html)在 Amazon Simple Queue Service API 参考中
+  [ListQueues](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_ListQueues.html)在 Amazon Simple Queue Service API 参考中
+  [DeleteQueue](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_DeleteQueue.html)在 Amazon Simple Queue Service API 参考中

# 发送、接收和删除 Amazon Simple Queue Service 消息
<a name="examples-sqs-messages"></a>

消息是可由分布式组件发送和接收的一段数据。始终使用 [SQS 队列](examples-sqs-message-queues.md)发送消息。

以下示例中使用的 `sqsClient` 变量可以通过以下代码段创建。

```
SqsClient sqsClient = SqsClient.create();
```

使用静态 `create()` 方法创建 `SqsClient` 时，SDK 使用[默认区域提供程序链](region-selection.md#default-region-provider-chain)配置区域，使用[默认凭证提供程序链](credentials-chain.md)配置凭证。

## 发送消息
<a name="sqs-message-send"></a>

通过调用 SqsClient 客户端`sendMessage`方法向 Amazon Simple Queue Service 队列中添加一条消息。提供一个包含队列的 [URL](examples-sqs-message-queues.md#sqs-get-queue-url)、消息正文和可选延迟值（以秒为单位）的[SendMessageRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sqs/model/SendMessageRequest.html)对象。

 **导入** 

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.sqs.SqsClient;
import software.amazon.awssdk.services.sqs.model.*;
import java.util.List;
```

 **代码** 

```
            sqsClient.sendMessage(SendMessageRequest.builder()
                .queueUrl(queueUrl)
                .messageBody("Hello world!")
                .delaySeconds(10)
                .build());

            sqsClient.sendMessage(sendMsgRequest);
```

## 在一个请求中发送多条消息
<a name="sqs-messages-send-multiple"></a>

通过使用 SqsClient `sendMessageBatch` 方法，在单个请求中发送多条消息。此方法采用[SendMessageBatchRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sqs/model/SendMessageBatchRequest.html)包含队列 URL 和要发送的消息列表的。（每条消息都是[SendMessageBatchRequestEntry](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sqs/model/SendMessageBatchRequestEntry.html)。） 您也可以通过设置消息上的延迟值来延迟发送特定消息。

 **导入** 

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.sqs.SqsClient;
import software.amazon.awssdk.services.sqs.model.*;
import java.util.List;
```

 **代码** 

```
            SendMessageBatchRequest sendMessageBatchRequest = SendMessageBatchRequest.builder()
                .queueUrl(queueUrl)
                .entries(SendMessageBatchRequestEntry.builder().id("id1").messageBody("Hello from msg 1").build(),
                        SendMessageBatchRequestEntry.builder().id("id2").messageBody("msg 2").delaySeconds(10).build())
                .build();
            sqsClient.sendMessageBatch(sendMessageBatchRequest);
```

请查看上[面的完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/cf25559da654a7b74bec039c0ab9397dc5951dd4/javav2/example_code/sqs/src/main/java/com/example/sqs/SQSExample.java#L133) GitHub。

## 检索消息
<a name="sqs-messages-receive"></a>

通过调用 SqsClient `receiveMessage` 方法，检索当前位于队列中的任何消息。此方法采用[ReceiveMessageRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sqs/model/ReceiveMessageRequest.html)包含队列 URL 的。您也可以指定要返回的消息的最大数量。消息将作为一系列 [Message](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sqs/model/Message.html) 对象返回。

 **导入** 

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.sqs.SqsClient;
import software.amazon.awssdk.services.sqs.model.*;
import java.util.List;
```

 **代码** 

```
        try {
            ReceiveMessageRequest receiveMessageRequest = ReceiveMessageRequest.builder()
                .queueUrl(queueUrl)
                .maxNumberOfMessages(5)
                .build();
            List<Message> messages = sqsClient.receiveMessage(receiveMessageRequest).messages();
            return messages;
        } catch (SqsException e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
        }
        return null;
```

请查看上[面的完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/cf25559da654a7b74bec039c0ab9397dc5951dd4/javav2/example_code/sqs/src/main/java/com/example/sqs/SQSExample.java#L148) GitHub。

## 收到后删除消息
<a name="sqs-messages-delete"></a>

在收到消息并处理其内容后，可通过将消息的接收句柄和队列 URL 发送到 `SqsClient's` [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sqs/SqsClient.html#deleteMessage(software.amazon.awssdk.services.sqs.model.DeleteMessageRequest)](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/sqs/SqsClient.html#deleteMessage(software.amazon.awssdk.services.sqs.model.DeleteMessageRequest)) 方法，以便从队列中删除消息。

 **导入** 

```
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.sqs.SqsClient;
import software.amazon.awssdk.services.sqs.model.*;
import java.util.List;
```

 **代码** 

```
        try {
            for (Message message : messages) {
                DeleteMessageRequest deleteMessageRequest = DeleteMessageRequest.builder()
                        .queueUrl(queueUrl)
                        .receiptHandle(message.receiptHandle())
                        .build();
                sqsClient.deleteMessage(deleteMessageRequest);
            }
```

请查看上[面的完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/cf25559da654a7b74bec039c0ab9397dc5951dd4/javav2/example_code/sqs/src/main/java/com/example/sqs/SQSExample.java#L187) GitHub。

## 更多信息
<a name="more-info"></a>
+  《 Amazon Simple Queue Service 开发者指南》中的@@ [Amazon Simple Queue Service 队列工作原](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-basic-architecture.html)理
+  [SendMessage](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_SendMessage.html)在 Amazon Simple Queue Service API 参考中
+  [SendMessageBatch](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_SendMessageBatch.html)在 Amazon Simple Queue Service API 参考中
+  [ReceiveMessage](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_ReceiveMessage.html)在 Amazon Simple Queue Service API 参考中
+  [DeleteMessage](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/APIReference/API_DeleteMessage.html)在 Amazon Simple Queue Service API 参考中

# 与... 一起工作 Amazon Transcribe
<a name="examples-transcribe"></a>

以下示例显示如何使用 Amazon Transcribe进行双向流式处理。双向流式处理意味着数据流同时转向服务且实时接收回来。该示例使用 Amazon Transcribe 流式转录发送音频流并实时接收转录的文本流。

要详细了解此功能，请参阅 Amazon Transcribe 开发者指南中的[流媒体转录](https://docs.aws.amazon.com//transcribe/latest/dg/streaming.html)。

[要开始使用](https://docs.aws.amazon.com//transcribe/latest/dg/getting-started.html)，请参阅 Amazon Transcribe 开发人员指南中的入门 Amazon Transcribe。

## 设置麦克风
<a name="set-up-the-microphone"></a>

此代码使用 javax.sound.sampled 软件包流式处理来自输入设备的音频。

 **代码** 

```
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.TargetDataLine;

public class Microphone {

    public static TargetDataLine get() throws Exception {
        AudioFormat format = new AudioFormat(16000, 16, 1, true, false);
        DataLine.Info datalineInfo = new DataLine.Info(TargetDataLine.class, format);

        TargetDataLine dataLine = (TargetDataLine) AudioSystem.getLine(datalineInfo);
        dataLine.open(format);

        return dataLine;
    }
}
```

请参阅上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/ac748d8ef99cd17e297cb74fe13aa671e2679088/javav2/example_code/transcribe/src/main/java/com/amazonaws/transcribe/Microphone.java) GitHub。

## 创建发布者
<a name="create-a-publisher"></a>

此代码实现了一个发布者，用于发布来自音频流的 Amazon Transcribe 音频数据。

 **代码** 

```
package com.amazonaws.transcribe;

import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.nio.ByteBuffer;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicLong;
import org.reactivestreams.Publisher;
import org.reactivestreams.Subscriber;
import org.reactivestreams.Subscription;
import software.amazon.awssdk.core.SdkBytes;
import software.amazon.awssdk.services.transcribestreaming.model.AudioEvent;
import software.amazon.awssdk.services.transcribestreaming.model.AudioStream;
import software.amazon.awssdk.services.transcribestreaming.model.TranscribeStreamingException;


public class AudioStreamPublisher implements Publisher<AudioStream> {
    private final InputStream inputStream;

    public AudioStreamPublisher(InputStream inputStream) {
        this.inputStream = inputStream;
    }

    @Override
    public void subscribe(Subscriber<? super AudioStream> s) {
        s.onSubscribe(new SubscriptionImpl(s, inputStream));
    }

    private class SubscriptionImpl implements Subscription {
        private static final int CHUNK_SIZE_IN_BYTES = 1024 * 1;
        private ExecutorService executor = Executors.newFixedThreadPool(1);
        private AtomicLong demand = new AtomicLong(0);

        private final Subscriber<? super AudioStream> subscriber;
        private final InputStream inputStream;

        private SubscriptionImpl(Subscriber<? super AudioStream> s, InputStream inputStream) {
            this.subscriber = s;
            this.inputStream = inputStream;
        }

        @Override
        public void request(long n) {
            if (n <= 0) {
                subscriber.onError(new IllegalArgumentException("Demand must be positive"));
            }

            demand.getAndAdd(n);

            executor.submit(() -> {
                try {
                    do {
                        ByteBuffer audioBuffer = getNextEvent();
                        if (audioBuffer.remaining() > 0) {
                            AudioEvent audioEvent = audioEventFromBuffer(audioBuffer);
                            subscriber.onNext(audioEvent);
                        } else {
                            subscriber.onComplete();
                            break;
                        }
                    } while (demand.decrementAndGet() > 0);
                } catch (TranscribeStreamingException e) {
                    subscriber.onError(e);
                }
            });
        }

        @Override
        public void cancel() {

        }

        private ByteBuffer getNextEvent() {
            ByteBuffer audioBuffer;
            byte[] audioBytes = new byte[CHUNK_SIZE_IN_BYTES];

            int len = 0;
            try {
                len = inputStream.read(audioBytes);

                if (len <= 0) {
                    audioBuffer = ByteBuffer.allocate(0);
                } else {
                    audioBuffer = ByteBuffer.wrap(audioBytes, 0, len);
                }
            } catch (IOException e) {
                throw new UncheckedIOException(e);
            }

            return audioBuffer;
        }

        private AudioEvent audioEventFromBuffer(ByteBuffer bb) {
            return AudioEvent.builder()
                    .audioChunk(SdkBytes.fromByteBuffer(bb))
                    .build();
        }
    }
}
```

请参阅上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/ac748d8ef99cd17e297cb74fe13aa671e2679088/javav2/example_code/transcribe/src/main/java/com/amazonaws/transcribe/AudioStreamPublisher.java) GitHub。

## 创建客户端和启动流
<a name="create-the-client-and-start-the-stream"></a>

在 main 方法中，创建请求对象，启动音频输入流，并通过音频输入实例化发布者。

您还必须创建[StartStreamTranscriptionResponseHandler](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/transcribestreaming/model/StartStreamTranscriptionResponseHandler.html)以指定如何处理来自的响应 Amazon Transcribe。

然后，使用 TranscribeStreamingAsyncClient's `startStreamTranscription` 方法开始双向流式传输。

 **导入** 

```
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.TargetDataLine;
import javax.sound.sampled.AudioInputStream;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.transcribestreaming.TranscribeStreamingAsyncClient;
import software.amazon.awssdk.services.transcribestreaming.model.TranscribeStreamingException ;
import software.amazon.awssdk.services.transcribestreaming.model.StartStreamTranscriptionRequest;
import software.amazon.awssdk.services.transcribestreaming.model.MediaEncoding;
import software.amazon.awssdk.services.transcribestreaming.model.LanguageCode;
import software.amazon.awssdk.services.transcribestreaming.model.StartStreamTranscriptionResponseHandler;
import software.amazon.awssdk.services.transcribestreaming.model.TranscriptEvent;
```

 **代码** 

```
    public static void convertAudio(TranscribeStreamingAsyncClient client) throws Exception {

        try {

            StartStreamTranscriptionRequest request = StartStreamTranscriptionRequest.builder()
                    .mediaEncoding(MediaEncoding.PCM)
                    .languageCode(LanguageCode.EN_US)
                    .mediaSampleRateHertz(16_000).build();

            TargetDataLine mic = Microphone.get();
            mic.start();

            AudioStreamPublisher publisher = new AudioStreamPublisher(new AudioInputStream(mic));

            StartStreamTranscriptionResponseHandler response =
                    StartStreamTranscriptionResponseHandler.builder().subscriber(e -> {
                        TranscriptEvent event = (TranscriptEvent) e;
                        event.transcript().results().forEach(r -> r.alternatives().forEach(a -> System.out.println(a.transcript())));
                    }).build();

            // Keeps Streaming until you end the Java program
            client.startStreamTranscription(request, publisher, response);

        } catch (TranscribeStreamingException e) {
            System.err.println(e.awsErrorDetails().errorMessage());
            System.exit(1);
         }
    }
```

请参阅上的[完整示例](https://github.com/awsdocs/aws-doc-sdk-examples/blob/ac748d8ef99cd17e297cb74fe13aa671e2679088/javav2/example_code/transcribe/src/main/java/com/amazonaws/transcribe/BidirectionalStreaming.java) GitHub。

## 更多信息
<a name="more-info"></a>
+  Amazon Transcribe 开发者指南中的@@ [工作原理](https://docs.aws.amazon.com//transcribe/latest/dg/how-it-works.html)。
+  《 Amazon Transcribe Developer Guide》中的 [Getting Started With Streaming Audio](https://docs.aws.amazon.com//transcribe/latest/dg/getting-started.html)。