使用Amazon SDK for Java 2.x 对 Amazon DynamoDB 进行编程 - Amazon DynamoDB
Amazon Web Services 文档中描述的 Amazon Web Services 服务或功能可能因区域而异。要查看适用于中国区域的差异,请参阅 中国的 Amazon Web Services 服务入门 (PDF)

使用Amazon SDK for Java 2.x 对 Amazon DynamoDB 进行编程

本指南为想要结合 Java 使用 Amazon DynamoDB 的程序员提供了指导。本指南涵盖了不同的概念,例如抽象层、配置管理、错误处理、控制重试策略和管理 keep-alive。

关于 Amazon SDK for Java 2.x

您可以使用官方的Amazon SDK for Java 从 Java 访问 DynamoDB。适用于 Java 的 SDK 有两个版本:1.x 和 2.x。对于 1.x,已于 2024 年 1 月 12 日宣布终止支持。它于 2024 年 7 月 31 日进入维护模式,其终止支持的日期为 2025 年 12 月 31 日。对于新开发,我们强烈建议使用 2018 年首次发布的 2.x。本指南专门针对 2.x,仅重点介绍 SDK 中与 DynamoDB 相关的部分。

有关 SDK 维护和支持的更多信息,请参阅《Amazon SDK 和工具参考指南》中的 Amazon SDK 和工具维护策略以及 Amazon SDK 和工具版本支持矩阵主题。

Amazon SDK for Java 2.x 是对 1.x 代码库的重大改写,以支持现代 Java 特征,例如 Java 8 中引入的非阻塞 I/O。Java 2.x SDK 还增加了对可插拔 HTTP 客户端实现的支持,从而为网络连接提供了更大的灵活性和可配置性。

从 Java 1.x SDK 到 Java 2.x SDK 的一个明显变化是软件包名称的更改。Java 1.x SDK 使用 com.amazonaws 软件包名称,而 Java 2.x SDK 使用 software.amazon.awssdk。同样,Java 1.x SDK 的 Maven 构件使用 com.amazonaws groupId,而 Java 2.x SDK 构件使用 software.amazon.awssdk groupId。

重要

Amazon SDK for Java 1.x 有一个名为 com.amazonaws.dynamodbv2 的 DynamoDB 软件包。软件包名称中的 v2 并不表示它是 Java v2。v2 表示该软件包支持 DynamoDB 低级别 API 的第二个版本,而不是低级别 API 的原始版本

Java 版本支持

Amazon SDK for Java 2.x 为长期支持(LTS)Java 版本提供全面支持。

开始使用 Amazon SDK for Java 2.x

以下教程向您展示如何使用 Apache Maven 为适用于 Java 的 SDK 2.x 定义依赖项。本教程还向您展示了如何编写连接到 DynamoDB 的代码,以列出可用的 DynamoDB 表。本教程基于 Amazon SDK for Java 2.x 入门。我们编辑本教程是为了调用 DynamoDB 而不是 Amazon S3。

步骤 1:为本教程进行设置

在开始本教程之前,您需要具备:

  • 访问 Amazon DynamoDB 的权限。

  • 一个 Java 开发环境,配置为使用 Amazon IAM Identity Center 单点登录访问 Amazon Web Services

请按照设置概述中的说明为本教程进行设置。在为 Java SDK 将开发环境配置为单点登录访问,并且 Amazon 访问门户会话处于活动状态后,请继续本教程的步骤 2

步骤 2:创建项目

要为本教程创建项目,您需要运行一条 Maven 命令,该命令会提示您输入有关如何配置项目的信息。完成所有输入并进行确认后,Maven 通过创建 pom.xml 文件并创建存根 Java 文件完成项目构建。

  1. 打开终端或命令提示符窗口,然后导航到您选择的目录,例如您的 DesktopHome 文件夹。

  2. 在终端中输入以下命令,然后按 Enter

    mvn archetype:generate \ -DarchetypeGroupId=software.amazon.awssdk \ -DarchetypeArtifactId=archetype-app-quickstart \ -DarchetypeVersion=2.22.0
  3. 为每个提示输入第二列中列出的值。

    提示 要输入的值
    Define value for property 'service': dynamodb
    Define value for property 'httpClient': apache-client
    Define value for property 'nativeImage': false
    Define value for property 'credentialProvider' identity-center
    Define value for property 'groupId': org.example
    Define value for property 'artifactId': getstarted
    Define value for property 'version' 1.0-SNAPSHOT: <Enter>
    Define value for property 'package' org.example: <Enter>
  4. 输入最后一个值后,Maven 会列出您所做的选择。通过输入 Y 进行确认,或者通过输入 N 重新输入值。

Maven 会根据您输入的 artifactId 值创建名为 getstarted 的项目文件夹。在 getstarted 文件夹中,找到一个可以查看的 README.md 文件、一个 pom.xml 文件和一个 src 目录。

Maven 构建了以下目录树。

getstarted ├── README.md ├── pom.xml └── src ├── main │ ├── java │ │ └── org │ │ └── example │ │ ├── App.java │ │ ├── DependencyFactory.java │ │ └── Handler.java │ └── resources │ └── simplelogger.properties └── test └── java └── org └── example └── HandlerTest.java 10 directories, 7 files

下面显示的是 pom.xml 项目文件的内容。

dependencyManagement 部分包含一个Amazon SDK for Java 2.x 依赖项,而 dependencies 部分包含一个 Amazon DynamoDB 依赖项。指定这些依赖项会强制 Maven 将相关的 jar 文件包含到您的 Java 类路径中。默认情况下,Amazon SDK 不包含所有 Amazon Web Services的所有类。对于 DynamoDB,如果您使用低级别接口,则应具有 dynamodb 构件依赖项,如果您使用高级别接口,则应具有 dynamodb-enhanced 构件依赖项。如果您不包含相关的依赖项,则不会编译您的代码。由于 maven.compiler.sourcemaven.compiler.target 属性中的值是 1.8,所以该项目使用 Java 1.8。

<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>org.example</groupId> <artifactId>getstarted</artifactId> <version>1.0-SNAPSHOT</version> <packaging>jar</packaging> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.8</maven.compiler.source> <maven.compiler.target>1.8</maven.compiler.target> <maven.shade.plugin.version>3.2.1</maven.shade.plugin.version> <maven.compiler.plugin.version>3.6.1</maven.compiler.plugin.version> <exec-maven-plugin.version>1.6.0</exec-maven-plugin.version> <aws.java.sdk.version>2.22.0</aws.java.sdk.version> <-------- SDK version picked up from archetype version. <slf4j.version>1.7.28</slf4j.version> <junit5.version>5.8.1</junit5.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>bom</artifactId> <version>${aws.java.sdk.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>dynamodb</artifactId> <-------- DynamoDB dependency <exclusions> <exclusion> <groupId>software.amazon.awssdk</groupId> <artifactId>netty-nio-client</artifactId> </exclusion> <exclusion> <groupId>software.amazon.awssdk</groupId> <artifactId>apache-client</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>sso</artifactId> <-------- Required for identity center authentication. </dependency> <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>ssooidc</artifactId> <-------- Required for identity center authentication. </dependency> <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>apache-client</artifactId> <-------- HTTP client specified. <exclusions> <exclusion> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>${slf4j.version}</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-simple</artifactId> <version>${slf4j.version}</version> </dependency> <!-- Needed to adapt Apache Commons Logging used by Apache HTTP Client to Slf4j to avoid ClassNotFoundException: org.apache.commons.logging.impl.LogFactoryImpl during runtime --> <dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> <version>${slf4j.version}</version> </dependency> <!-- Test Dependencies --> <dependency> <groupId>org.junit.jupiter</groupId> <artifactId>junit-jupiter</artifactId> <version>${junit5.version}</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>${maven.compiler.plugin.version}</version> </plugin> </plugins> </build> </project>

步骤 3:编写代码

以下代码显示的是 Maven 创建的 App 类。main 方法是应用程序的入口点,它会创建 Handler 类的实例,然后调用其 sendRequest 方法。

package org.example; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class App { private static final Logger logger = LoggerFactory.getLogger(App.class); public static void main(String... args) { logger.info("Application starts"); Handler handler = new Handler(); handler.sendRequest(); logger.info("Application ends"); } }

Maven 创建的 DependencyFactory 类包含用于构建和返回 DynamoDbClient 实例的 dynamoDbClient 工厂方法。DynamoDbClient 实例使用基于 Apache 的 HTTP 客户端的实例。这是因为您在 Maven 提示您输入使用哪个 HTTP 客户端时指定了 apache-client

以下代码中显示了 DependencyFactory

package org.example; import software.amazon.awssdk.http.apache.ApacheHttpClient; import software.amazon.awssdk.services.dynamodb.DynamoDbClient; /** * The module containing all dependencies required by the {@link Handler}. */ public class DependencyFactory { private DependencyFactory() {} /** * @return an instance of DynamoDbClient */ public static DynamoDbClient dynamoDbClient() { return DynamoDbClient.builder() .httpClientBuilder(ApacheHttpClient.builder()) .build(); } }

Handler 类包含程序的主要逻辑。在 App 类中创建 Handler 的实例时,DependencyFactory 将提供 DynamoDbClient 服务客户端。您的代码使用 DynamoDbClient 实例调用 DynamoDB 服务。

Maven 生成以下带有 TODO 注释的 Handler 类。本教程的下一步会将 TODO 替换为代码。

package org.example; import software.amazon.awssdk.services.dynamodb.DynamoDbClient; public class Handler { private final DynamoDbClient dynamoDbClient; public Handler() { dynamoDbClient = DependencyFactory.dynamoDbClient(); } public void sendRequest() { // TODO: invoking the API calls using dynamoDbClient. } }

要填写逻辑,请将该 Handler 类的全部内容替换为以下代码。这将填写 sendRequest 方法并添加必要的导入。

以下代码使用 DynamoDbClient 实例检索现有表的列表。如果给定账户和区域存在表,则代码将使用 Logger 实例记录这些表的名称。

package org.example; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import software.amazon.awssdk.services.dynamodb.DynamoDbClient; import software.amazon.awssdk.services.dynamodb.model.ListTablesResponse; public class Handler { private final DynamoDbClient dynamoDbClient; public Handler() { dynamoDbClient = DependencyFactory.dynamoDbClient(); } public void sendRequest() { Logger logger = LoggerFactory.getLogger(Handler.class); logger.info("calling the DynamoDB API to get a list of existing tables"); ListTablesResponse response = dynamoDbClient.listTables(); if (!response.hasTableNames()) { logger.info("No existing tables found for the configured account & region"); } else { response.tableNames().forEach(tableName -> logger.info("Table: " + tableName)); } } }

步骤 4:构建并运行应用程序

在创建了包含完整 Handler 类的项目后,生成并运行该应用程序。

  1. 确保您的 IAM Identity Center 会话处于活动状态。为此,请运行 Amazon Command Line Interface 命令 aws sts get-caller-identity 并检查响应。如果您没有活动会话,请参阅使用 Amazon CLI 登录了解说明。

  2. 打开终端或命令提示符窗口并导航至您的项目目录 getstarted

  3. 使用以下命令生成项目:

    mvn clean package
  4. 使用以下命令运行应用程序。

    mvn exec:java -Dexec.mainClass="org.example.App"

查看文件后,删除对象,然后删除桶。

成功

如果您的 Maven 项目生成和运行都没有错误,那么恭喜您!您已经使用适用于 Java 的 SDK 2.x 成功构建了您的第一个 Java 应用程序。

清理

要清除您在本教程中创建的资源,请执行以下操作:

  • 删除项目文件夹 getstarted

使用 Amazon SDK for Java 2.x 文档

Amazon SDK for Java 2.x 文档涵盖了所有 Amazon Web Services的 SDK 的所有方面。请先了解以下主题:

提示

我们建议您在阅读完Amazon SDK for Java 2.x 文档中的这些主题后,将 Javadoc 文档添加为书签。它涵盖了所有 Amazon Web Services,并将作为您的主要 API 参考。

支持的接口

根据您想要的抽象级别,Amazon SDK for Java 2.x 支持以下接口。

低级别接口

低级别接口提供与底层服务 API 的一对一映射。每个 DynamoDB API 都可通过此接口提供。这意味着低级别接口可以提供完整的功能,但使用起来往往更加冗长且复杂。例如,您必须使用 .s() 函数来保存字符串,使用 .n() 函数保存数字。以下 PutItem 示例使用低级别接口插入项目。

import org.slf4j.*; import software.amazon.awssdk.http.crt.AwsCrtHttpClient; import software.amazon.awssdk.services.dynamodb.DynamoDbClient; import software.amazon.awssdk.services.dynamodb.model.*; import java.util.Map; public class PutItem { // Create a DynamoDB client with the default settings connected to the DynamoDB // endpoint in the default region based on the default credentials provider chain. private static final DynamoDbClient DYNAMODB_CLIENT = DynamoDbClient.create(); private static final Logger LOGGER = LoggerFactory.getLogger(PutItem.class); private void putItem() { PutItemResponse response = DYNAMODB_CLIENT.putItem(PutItemRequest.builder() .item(Map.of( "pk", AttributeValue.builder().s("123").build(), "sk", AttributeValue.builder().s("cart#123").build(), "item_data", AttributeValue.builder().s("YourItemData").build(), "inventory", AttributeValue.builder().n("500").build() // ... more attributes ... )) .returnConsumedCapacity(ReturnConsumedCapacity.TOTAL) .tableName("YourTableName") .build()); LOGGER.info("PutItem call consumed [" + response.consumedCapacity().capacityUnits() + "] Write Capacity Unites (WCU)"); } }

高级别接口

Amazon SDK for Java 2.x 中的高级别接口称为 DynamoDB 增强型客户端。此接口提供了更为惯用的代码编写体验。

增强型客户端提供了一种在客户端数据类和专为存储该数据而设计的 DynamoDB 表之间进行映射的方法。您可以在代码中定义表与其相应模型类之间的关系。然后,您可以依靠 SDK 来管理数据类型操作。有关增强型客户端的更多信息,请参阅《Amazon SDK for Java 2.x 文档》中的 DynamoDB 增强型客户端 API

以下 PutItem 示例使用高级别接口。在此示例中,名为 YourItemDynamoDbBean 创建了一个 TableSchema,允许其直接用作 putItem() 调用的输入。

import org.slf4j.*; import software.amazon.awssdk.enhanced.dynamodb.*; import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.*; import software.amazon.awssdk.enhanced.dynamodb.model.*; import software.amazon.awssdk.services.dynamodb.model.ReturnConsumedCapacity; public class DynamoDbEnhancedClientPutItem { private static final DynamoDbEnhancedClient ENHANCED_DYNAMODB_CLIENT = DynamoDbEnhancedClient.builder().build(); private static final DynamoDbTable<YourItem> DYNAMODB_TABLE = ENHANCED_DYNAMODB_CLIENT.table("YourTableName", TableSchema.fromBean(YourItem.class)); private static final Logger LOGGER = LoggerFactory.getLogger(PutItem.class); private void putItem() { PutItemEnhancedResponse<YourItem> response = DYNAMODB_TABLE.putItemWithResponse(PutItemEnhancedRequest.builder(YourItem.class) .item(new YourItem("123", "cart#123", "YourItemData", 500)) .returnConsumedCapacity(ReturnConsumedCapacity.TOTAL) .build()); LOGGER.info("PutItem call consumed [" + response.consumedCapacity().capacityUnits() + "] Write Capacity Unites (WCU)"); } @DynamoDbBean public static class YourItem { public YourItem() {} public YourItem(String pk, String sk, String itemData, int inventory) { this.pk = pk; this.sk = sk; this.itemData = itemData; this.inventory = inventory; } private String pk; private String sk; private String itemData; private int inventory; @DynamoDbPartitionKey public void setPk(String pk) { this.pk = pk; } public String getPk() { return pk; } @DynamoDbSortKey public void setSk(String sk) { this.sk = sk; } public String getSk() { return sk; } public void setItemData(String itemData) { this.itemData = itemData; } public String getItemData() { return itemData; } public void setInventory(int inventory) { this.inventory = inventory; } public int getInventory() { return inventory; } } }

Amazon SDK for Java 1.x 有自己的高级别接口,通常由其主类 DynamoDBMapper 引用。Amazon SDK for Java 2.x 发布在名为 software.amazon.awssdk.enhanced.dynamodb 的单独软件包(和 Maven 构件)中。Java 2.x SDK 通常由其主类 DynamoDbEnhancedClient 引用。

使用不可变数据类的高级别接口

DynamoDB 增强型客户端 API 的映射特征也适用于不可变的数据类。不可变类只有 getter,且需要一个生成器类,使 SDK 可用来创建该类的实例。Java 中的不可变性是一种常用的样式,它允许开发人员创建无副作用的类,因此在复杂的多线程应用程序中,其行为更具可预测性。不可变类不使用High-level interface example中所示的 @DynamoDbBean 注释,而是使用 @DynamoDbImmutable 注释,该注释采用生成器类作为其输入。

以下示例使用生成器类 DynamoDbEnhancedClientImmutablePutItem 作为输入来创建表架构。然后,该示例提供架构作为 PutItem API 调用的输入。

import org.slf4j.*; import software.amazon.awssdk.enhanced.dynamodb.*; import software.amazon.awssdk.enhanced.dynamodb.model.*; import software.amazon.awssdk.services.dynamodb.model.ReturnConsumedCapacity; public class DynamoDbEnhancedClientImmutablePutItem { private static final DynamoDbEnhancedClient ENHANCED_DYNAMODB_CLIENT = DynamoDbEnhancedClient.builder().build(); private static final DynamoDbTable<YourImmutableItem> DYNAMODB_TABLE = ENHANCED_DYNAMODB_CLIENT.table("YourTableName", TableSchema.fromImmutableClass(YourImmutableItem.class)); private static final Logger LOGGER = LoggerFactory.getLogger(DynamoDbEnhancedClientImmutablePutItem.class); private void putItem() { PutItemEnhancedResponse<YourImmutableItem> response = DYNAMODB_TABLE.putItemWithResponse(PutItemEnhancedRequest.builder(YourImmutableItem.class) .item(YourImmutableItem.builder() .pk("123") .sk("cart#123") .itemData("YourItemData") .inventory(500) .build()) .returnConsumedCapacity(ReturnConsumedCapacity.TOTAL) .build()); LOGGER.info("PutItem call consumed [" + response.consumedCapacity().capacityUnits() + "] Write Capacity Unites (WCU)"); } }

以下示例展示了不可变数据类。

@DynamoDbImmutable(builder = YourImmutableItem.YourImmutableItemBuilder.class) class YourImmutableItem { private final String pk; private final String sk; private final String itemData; private final int inventory; public YourImmutableItem(YourImmutableItemBuilder builder) { this.pk = builder.pk; this.sk = builder.sk; this.itemData = builder.itemData; this.inventory = builder.inventory; } public static YourImmutableItemBuilder builder() { return new YourImmutableItemBuilder(); } @DynamoDbPartitionKey public String getPk() { return pk; } @DynamoDbSortKey public String getSk() { return sk; } public String getItemData() { return itemData; } public int getInventory() { return inventory; } static final class YourImmutableItemBuilder { private String pk; private String sk; private String itemData; private int inventory; private YourImmutableItemBuilder() {} public YourImmutableItemBuilder pk(String pk) { this.pk = pk; return this; } public YourImmutableItemBuilder sk(String sk) { this.sk = sk; return this; } public YourImmutableItemBuilder itemData(String itemData) { this.itemData = itemData; return this; } public YourImmutableItemBuilder inventory(int inventory) { this.inventory = inventory; return this; } public YourImmutableItem build() { return new YourImmutableItem(this); } } }

使用不可变数据类和第三方样板生成库的高级别接口

上一部分中提到的不可变数据类示例需要一些样板代码。例如,生成器类之外的数据类上的 getter 和 setter 逻辑。第三方库(例如 Project Lombok)有助于您生成此类样板代码。

Amazon SDK for Java 2.x 文档中的使用第三方库(例如 Lombok)部分介绍了利用 Project Lombok 库的概念。减少大部分样板代码有助于限制使用不可变数据类和 Amazon SDK 所需的代码量。这进一步提高了代码的编写效率和可读性。

以下示例展示了 Project Lombok 如何简化使用 DynamoDB 增强型客户端 API 所需的代码。

import org.slf4j.*; import software.amazon.awssdk.enhanced.dynamodb.*; import software.amazon.awssdk.enhanced.dynamodb.model.*; import software.amazon.awssdk.services.dynamodb.model.ReturnConsumedCapacity; public class DynamoDbEnhancedClientImmutableLombokPutItem { private static final DynamoDbEnhancedClient ENHANCED_DYNAMODB_CLIENT = DynamoDbEnhancedClient.builder().build(); private static final DynamoDbTable<YourImmutableLombokItem> DYNAMODB_TABLE = ENHANCED_DYNAMODB_CLIENT.table("YourTableName", TableSchema.fromImmutableClass(YourImmutableLombokItem.class)); private static final Logger LOGGER = LoggerFactory.getLogger(DynamoDbEnhancedClientImmutableLombokPutItem.class); private void putItem() { PutItemEnhancedResponse<YourImmutableLombokItem> response = DYNAMODB_TABLE.putItemWithResponse(PutItemEnhancedRequest.builder(YourImmutableLombokItem.class) .item(YourImmutableLombokItem.builder() .pk("123") .sk("cart#123") .itemData("YourItemData") .inventory(500) .build()) .returnConsumedCapacity(ReturnConsumedCapacity.TOTAL) .build()); LOGGER.info("PutItem call consumed [" + response.consumedCapacity().capacityUnits() + "] Write Capacity Unites (WCU)"); } }

以下示例展示了不可变数据类的不可变数据对象。

import lombok.*; import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.*; @Builder @DynamoDbImmutable(builder = YourImmutableLombokItem.YourImmutableLombokItemBuilder.class) @Value public class YourImmutableLombokItem { @Getter(onMethod_=@DynamoDbPartitionKey) String pk; @Getter(onMethod_=@DynamoDbSortKey) String sk; String itemData; int inventory; }

YourImmutableLombokItem 类使用 Project Lombok 和 Amazon SDK 提供的以下注释:

  • @Builder — 为 Project Lombok 提供的数据类生成复杂的生成器 API。

  • @DynamoDbImmutable — 将 DynamoDbImmutable 类标识为 Amazon SDK 提供的 DynamoDB 可映射实体注释。

  • @Value@Data 的不可变变体;默认情况下,所有字段均为私有字段和最终字段,并且不会生成 setter。本注释由 Project Lombok 提供。

文档接口

文档接口无需指定数据类型描述符。数据类型由数据本身的语义隐含。如果您熟悉 Amazon SDK for Java 1.x D文档接口,Amazon SDK for Java 2.x 中的文档接口提供了类似但经过重新设计的接口。

下面的 Document interface example显示了使用文档接口表达的 PutItem 调用。该示例还使用了 EnhancedDocument。要使用增强型文档 API 对 DynamoDB 表执行命令,必须先将该表与您的文档表架构关联,来创建一个 DynamoDBTable 资源对象。Document 表架构生成器需要一个主索引键和一个或多个属性转换器提供程序。

您可以使用 AttributeConverterProvider.defaultProvider() 转换默认类型的文档属性。您可以使用自定义 AttributeConverterProvider 实现来更改整体默认行为。您还可以更改单个属性的转换器。Amazon SDK 文档提供了有关如何使用自定义转换器的更多详细信息和示例。它们的主要用途是用于没有默认转换器的域类的属性。使用自定义转换器,您可以为 SDK 提供写入或读取 DynamoDB 所需的信息。

import org.slf4j.*; import software.amazon.awssdk.enhanced.dynamodb.*; import software.amazon.awssdk.enhanced.dynamodb.document.EnhancedDocument; import software.amazon.awssdk.enhanced.dynamodb.model.*; import software.amazon.awssdk.services.dynamodb.model.ReturnConsumedCapacity; public class DynamoDbEnhancedDocumentClientPutItem { private static final DynamoDbEnhancedClient ENHANCED_DYNAMODB_CLIENT = DynamoDbEnhancedClient.builder().build(); private static final DynamoDbTable<EnhancedDocument> DYNAMODB_TABLE = ENHANCED_DYNAMODB_CLIENT.table("YourTableName", TableSchema.documentSchemaBuilder() .addIndexPartitionKey(TableMetadata.primaryIndexName(),"pk", AttributeValueType.S) .addIndexSortKey(TableMetadata.primaryIndexName(), "sk", AttributeValueType.S) .attributeConverterProviders(AttributeConverterProvider.defaultProvider()) .build()); private static final Logger LOGGER = LoggerFactory.getLogger(DynamoDbEnhancedDocumentClientPutItem.class); private void putItem() { PutItemEnhancedResponse<EnhancedDocument> response = DYNAMODB_TABLE.putItemWithResponse( PutItemEnhancedRequest.builder(EnhancedDocument.class) .item( EnhancedDocument.builder() .attributeConverterProviders(AttributeConverterProvider.defaultProvider()) .putString("pk", "123") .putString("sk", "cart#123") .putString("item_data", "YourItemData") .putNumber("inventory", 500) .build()) .returnConsumedCapacity(ReturnConsumedCapacity.TOTAL) .build()); LOGGER.info("PutItem call consumed [" + response.consumedCapacity().capacityUnits() + "] Write Capacity Unites (WCU)"); } }

使用以下实用程序方法,您可以在 JSON 文档和本机 Amazon DynamoDB 数据类型之间相互转换:

将接口与 Query 示例进行比较

本部分展示了使用各种接口表达的相同查询调用。这些查询使用几个属性来微调查询结果:

  • 您必须完全指定分区键,因为 DynamoDB 将锁定一个特定的分区键值。

  • 排序键有一个使用 begins_with 的键条件表达式,因此此查询仅锁定购物车商品。

  • 我们使用 limit() 将查询限制为最多返回 100 个商品。

  • 我们将 scanIndexForward 设置为 false。结果按照 UTF-8 字节的顺序返回,这通常意味着首先返回编号最小的购物车商品。通过将 scanIndexForward 设置为 false,我们可以颠倒顺序,首先返回编号最大的购物车商品。

  • 我们会应用筛选条件来删除任何不符合条件的结果。无论商品是否与筛选条件匹配,筛选的数据都会消耗读取容量。

例 使用低级别接口的查询

以下示例使用 keyConditionExpression 查询名为 YourTableName 的表,这会将查询限制为特定的分区键值和以特定前缀值开头的排序键值。这些关键条件限制了从 DynamoDB 读取的数据量。最后,该查询使用 filterExpression 筛选从 DynamoDB 检索的数据。

import org.slf4j.*; import software.amazon.awssdk.services.dynamodb.DynamoDbClient; import software.amazon.awssdk.services.dynamodb.model.*; import java.util.Map; public class Query { // Create a DynamoDB client with the default settings connected to the DynamoDB // endpoint in the default region based on the default credentials provider chain. private static final DynamoDbClient DYNAMODB_CLIENT = DynamoDbClient.builder().build(); private static final Logger LOGGER = LoggerFactory.getLogger(Query.class); private static void query() { QueryResponse response = DYNAMODB_CLIENT.query(QueryRequest.builder() .expressionAttributeNames(Map.of("#name", "name")) .expressionAttributeValues(Map.of( ":pk_val", AttributeValue.fromS("id#1"), ":sk_val", AttributeValue.fromS("cart#"), ":name_val", AttributeValue.fromS("SomeName"))) .filterExpression("#name = :name_val") .keyConditionExpression("pk = :pk_val AND begins_with(sk, :sk_val)") .limit(100) .scanIndexForward(false) .tableName("YourTableName") .build()); LOGGER.info("nr of items: " + response.count()); LOGGER.info("First item pk: " + response.items().get(0).get("pk")); LOGGER.info("First item sk: " + response.items().get(0).get("sk")); } }
例 使用文档接口的查询

以下示例使用文档接口查询名为 YourTableName 的表。

import org.slf4j.Logger; import org.slf4j.LoggerFactory; import software.amazon.awssdk.enhanced.dynamodb.*; import software.amazon.awssdk.enhanced.dynamodb.document.EnhancedDocument; import software.amazon.awssdk.enhanced.dynamodb.model.*; import java.util.Map; public class DynamoDbEnhancedDocumentClientQuery { // Create a DynamoDB client with the default settings connected to the DynamoDB // endpoint in the default region based on the default credentials provider chain. private static final DynamoDbEnhancedClient ENHANCED_DYNAMODB_CLIENT = DynamoDbEnhancedClient.builder().build(); private static final DynamoDbTable<EnhancedDocument> DYNAMODB_TABLE = ENHANCED_DYNAMODB_CLIENT.table("YourTableName", TableSchema.documentSchemaBuilder() .addIndexPartitionKey(TableMetadata.primaryIndexName(),"pk", AttributeValueType.S) .addIndexSortKey(TableMetadata.primaryIndexName(), "sk", AttributeValueType.S) .attributeConverterProviders(AttributeConverterProvider.defaultProvider()) .build()); private static final Logger LOGGER = LoggerFactory.getLogger(DynamoDbEnhancedDocumentClientQuery.class); private void query() { PageIterable<EnhancedDocument> response = DYNAMODB_TABLE.query(QueryEnhancedRequest.builder() .filterExpression(Expression.builder() .expression("#name = :name_val") .expressionNames(Map.of("#name", "name")) .expressionValues(Map.of(":name_val", AttributeValue.fromS("SomeName"))) .build()) .limit(100) .queryConditional(QueryConditional.sortBeginsWith(Key.builder() .partitionValue("id#1") .sortValue("cart#") .build())) .scanIndexForward(false) .build()); LOGGER.info("nr of items: " + response.items().stream().count()); LOGGER.info("First item pk: " + response.items().iterator().next().getString("pk")); LOGGER.info("First item sk: " + response.items().iterator().next().getString("sk")); } }
例 使用高级别接口的查询

以下示例使用 DynamoDB 增强型客户端 API 查询名为 YourTableName 的表。

import org.slf4j.*; import software.amazon.awssdk.enhanced.dynamodb.*; import software.amazon.awssdk.enhanced.dynamodb.mapper.annotations.*; import software.amazon.awssdk.enhanced.dynamodb.model.*; import software.amazon.awssdk.services.dynamodb.model.AttributeValue; import java.util.Map; public class DynamoDbEnhancedClientQuery { private static final DynamoDbEnhancedClient ENHANCED_DYNAMODB_CLIENT = DynamoDbEnhancedClient.builder().build(); private static final DynamoDbTable<YourItem> DYNAMODB_TABLE = ENHANCED_DYNAMODB_CLIENT.table("YourTableName", TableSchema.fromBean(DynamoDbEnhancedClientQuery.YourItem.class)); private static final Logger LOGGER = LoggerFactory.getLogger(DynamoDbEnhancedClientQuery.class); private void query() { PageIterable<YourItem> response = DYNAMODB_TABLE.query(QueryEnhancedRequest.builder() .filterExpression(Expression.builder() .expression("#name = :name_val") .expressionNames(Map.of("#name", "name")) .expressionValues(Map.of(":name_val", AttributeValue.fromS("SomeName"))) .build()) .limit(100) .queryConditional(QueryConditional.sortBeginsWith(Key.builder() .partitionValue("id#1") .sortValue("cart#") .build())) .scanIndexForward(false) .build()); LOGGER.info("nr of items: " + response.items().stream().count()); LOGGER.info("First item pk: " + response.items().iterator().next().getPk()); LOGGER.info("First item sk: " + response.items().iterator().next().getSk()); } @DynamoDbBean public static class YourItem { public YourItem() {} public YourItem(String pk, String sk, String name) { this.pk = pk; this.sk = sk; this.name = name; } private String pk; private String sk; private String name; @DynamoDbPartitionKey public void setPk(String pk) { this.pk = pk; } public String getPk() { return pk; } @DynamoDbSortKey public void setSk(String sk) { this.sk = sk; } public String getSk() { return sk; } public void setName(String name) { this.name = name; } public String getName() { return name; } } }
使用不可变数据类的高级别接口

使用高级别不可变数据类执行查询时,除了实体类 YourItemYourImmutableItem 的构造之外,代码与高级别接口示例相同。有关更多信息,请参阅 PutItem 示例。

使用不可变数据类和第三方样板生成库的高级别接口

使用高级别不可变数据类执行查询时,除了实体类 YourItemYourImmutableLombokItem 的构造之外,代码与高级别接口示例相同。有关更多信息,请参阅 PutItem 示例。

其它代码示例

您还可以参考以下代码示例存储库,其中提供了将 DynamoDB 与 适用于 Java 的 SDK 2.x 配合使用的其它示例:

同步和异步编程

Amazon SDK for Java 2.x 为 DynamoDB 等 Amazon Web Services提供了同步异步客户端。

DynamoDbClientDynamoDbEnhancedClient 类提供了同步方法,这些方法会阻止执行您的线程,直到客户端接收到服务的响应。如果您不需要异步操作,则此客户端是与 DynamoDB 交互的最直接方式。

DynamoDbAsyncClientDynamoDbEnhancedAsyncClient 类提供了异步方法,这些方法会立即返回,并控制调用的线程,而不必等待响应。非阻塞客户端的优势在于,它允许在几个线程之间实现高并发性,从而以最少的计算资源高效处理 I/O 请求。这会提高吞吐量和响应能力。

Amazon SDK for Java 2.x 使用对非阻塞 I/O 的本机支持。Amazon SDK for Java 1.x 必须模拟非阻塞 I/O。

由于同步方法在收到响应之前返回,所以需要通过某种方法在响应准备就绪时接收响应。Amazon SDK for Java 中的异步方法会返回 CompletableFuture 对象,其中包含未来的异步操作的结果。当您在在这些 CompletableFuture 对象上调用 get()join() 时,您的代码将阻塞,直到结果可用。如果您在发出请求的同时进行此调用,则其行为与普通的同步调用类似。

异步编程的 Amazon SDK 文档提供了有关如何利用这种异步样式的更多详细信息。

HTTP 客户端

为了支持每个客户端,有一个处理与 Amazon Web Services的通信的 HTTP 客户端。您可以插入备用 HTTP 客户端,选择一个具有最适合您应用程序的特性的客户端。有些更轻量;有些则有更多的配置选项。

有些 HTTP 客户端仅支持同步使用,而另一些则仅支持异步使用。Amazon SDK 文档提供了一个流程图,有助于您为工作负载选择最佳 HTTP 客户端。

以下列表列出了一些可能的 HTTP 客户端:

基于 Apache 的 HTTP 客户端

ApacheHttpClient 支持同步服务客户端,是同步使用的默认 HTTP 客户端。有关配置 ApacheHttpClient 的信息,请参阅Amazon SDK for Java 2.x 文档中的配置基于 Apache 的 HTTP 客户端

基于 URLConnection 的 HTTP 客户端

UrlConnectionHttpClient 是同步客户端的另一种选择。其加载速度比基于 Apache 的 HTTP 客户端快,但特征较少。有关配置 UrlConnectionHttpClient 的信息,请参阅配置基于 URLConnection 的 HTTP 客户端

基于 Netty 的 HTTP 客户端

NettyNioAsyncHttpClient 支持异步客户端,是异步使用的默认选项。有关配置 NettyNioAsyncHttpClient 的信息,请参阅配置基于 Netty 的 HTTP 客户端

基于 Amazon CRT 的 HTTP 客户端

Amazon 公共运行时(CRT)库中的较新 AwsCrtHttpClientAwsCrtAsyncHttpClient 是同时支持同步和异步客户端的另一种选项。与其它 HTTP 客户端相比,Amazon CRT 可提供:

  • 更快的 SDK 启动时间

  • 更小的内存占用空间

  • 降低的延迟时间

  • 连接运行状况管理

  • DNS 负载均衡

有关配置 AwsCrtHttpClientAwsCrtAsyncHttpClient 的信息,请参阅配置基于 Amazon CRT 的 HTTP 客户端

基于 Amazon CRT 的 HTTP 客户端不是默认选项,因为其会破坏现有应用程序的向后兼容性。但是,对于 DynamoDB,不管是同步使用还是异步使用,我们都建议使用基于 Amazon CRT 的 HTTP 客户端。

有关基于 Amazon CRT 的 HTTP 客户端的介绍,请参阅 Announcing availability of the Amazon CRT HTTP Client in the Amazon SDK for Java 2.x

配置 HTTP 客户端

配置客户端时,您可以提供各种配置选项,包括:

  • 为 API 调用的不同方面设置超时

  • 控制是否启用 TCP Keep-Alive

  • 控制遇到错误时的重试策略

  • 指定执行拦截器实例可以修改的执行属性。执行拦截器可以编写代码,拦截 API 请求和响应的执行。这使您能够执行任务,例如发布指标和修改动态请求。

  • 添加或操作 HTTP 标头

  • 启用对客户端性能指标的跟踪。使用此特征有助于您收集应用程序中服务客户端的指标,并在 Amazon CloudWatch 中分析输出。

  • 指定用于调度任务(例如异步重试尝试和超时任务)的备用执行器服务

您可以通过向服务客户端 Builder 类提供 ClientOverrideConfiguration 对象来控制配置。您将在以下部分的一些代码示例中看到这一点。

ClientOverrideConfiguration 提供了标准配置选项。不同的可插拔 HTTP 客户端也有实现特定的配置可能性。这些客户端还维护有自己的文档:

超时配置

您可以调整客户端配置,来控制与服务调用相关的各种超时。与其它 Amazon Web Services相比,DynamoDB 的延迟更低。因此,您可能需要将这些属性调整为较低的超时值,以便在出现网络问题时可以快速失效。

您可以在 DynamoDB 客户端上使用 ClientOverrideConfiguration 或通过更改底层 HTTP 客户端实现的详细配置选项来自定义与延迟相关的行为。

您可以使用 ClientOverrideConfiguration 配置以下有影响力的属性:

  • apiCallAttemptTimeout—在放弃和超时之前,等待单次 HTTP 请求尝试完成的时间。

  • apiCallTimeout—允许客户端完成 API 调用的执行所需的时间。这包括由所有 HTTP 请求(包括重试)组成的请求处理程序执行。

Amazon SDK for Java 2.x 为某些超时选项(例如连接超时和套接字超时等)提供了默认值。SDK 不为 API 调用超时或单个 API 调用尝试超时提供默认值。如果未在 ClientOverrideConfiguration 中设置这些超时,SDK 将改用套接字超时值作为整体 API 调用超时。套接字超时的默认值为 30 秒。

RetryMode

您应该考虑的另一个与超时配置相关的配置是 RetryMode 配置对象。此配置对象包含一组重试行为。

适用于 Java 的 SDK 2.x 支持以下重试模式:

  • legacy — 默认重试模式,如果您未明确更改。这种重试模式特定于 Java SDK,其特点是:

    • 对于服务,最多可重试三次或以上, 例如 DynamoDB 最多可重试 8 次。

  • standard — 之所以命名为标准版,是因为它与其它 Amazon SDK 更加一致。对于首次重试,此模式随机等待 0 毫秒到 1000 毫秒不等的时间。如果需要再次重试,它会从 0 毫秒到 1000 毫秒之间随机选择另一个时间,然后将其乘以二。如果需要更多重试,它会进行相同的随机选择,然后乘以 4,依此类推。每次等待的上限为 20 秒。与 legacy 模式相比,此模式将对更多检测到的故障条件执行重试。对于 DynamoDB,除非您使用 numRetries 覆盖,否则它最多总共可执行三次尝试。

  • adaptive — 基于 standard 模式构建,动态限制 Amazon 请求速率以最大限度地提高成功率。这样做可能会以牺牲请求延迟为代价。当可预测的延迟很重要时,我们不建议使用自适应重试模式。

您可以在《Amazon SDK 和工具参考指南》的重试行为主题中找到这些重试模式的扩展定义。

重试策略

所有 RetryMode 配置都有一个 RetryPolicy,后者是基于一个或多个 RetryCondition 配置构建的。TokenBucketRetryCondition 对于 DynamoDB SDK 客户端实现的重试行为尤其重要。此条件限制了 SDK 使用令牌存储桶算法进行的重试次数。根据所选的重试模式,节流异常可能会,也可能不会从 TokenBucket 中减去令牌。

当客户端遇到可重试的错误(例如节流异常或临时服务器错误)时,SDK 将自动重试请求。您可以控制这些重试发生的次数和频率。

配置客户端时,您可以提供支持以下参数的 RetryPolicy:

  • numRetries—在认为请求失败之前应当应用的最大重试次数。无论您使用哪种重试模式,默认值都是 8。

    警告

    请务必在适当考虑后更改此默认值。

  • backoffStrategy—应用于重试的 BackoffStrategy,其中 FullJitterBackoffStrategy 是默认策略。此策略根据当前的重试次数、基本延迟和最大回退时间,在额外重试之间执行指数延迟。然后它会添加抖动以提供一点随机性。无论重试模式如何,指数延迟中使用的基本延迟均为 25 毫秒。

  • retryConditionRetryCondition 决定是否应该重试请求。默认情况下,它将重试一组它认为可以重试的特定 HTTP 状态码和异常。在大多数情况下,默认配置应该足够了。

以下代码提供了另一种重试策略。它指定允许总共五次重试(总共六次请求)。首次重试应在大约 100 毫秒延迟之后进行,每增加一次重试,该时间成倍增加,最多延迟一秒。

DynamoDbClient client = DynamoDbClient.builder() .overrideConfiguration(ClientOverrideConfiguration.builder() .retryPolicy(RetryPolicy.builder() .backoffStrategy(FullJitterBackoffStrategy.builder() .baseDelay(Duration.ofMillis(100)) .maxBackoffTime(Duration.ofSeconds(1)) .build()) .numRetries(5) .build()) .build()) .build();

DefaultsMode

超时属性不是由 ClientOverrideConfiguration 管理的,RetryMode 通常不会显式配置。相反,它们的配置是通过指定 DefaultsMode 来隐式设置的。

Amazon SDK for Java 2.x(版本 2.17.102 或更高版本)中引入了对 DefaultsMode 的支持,为常见的可配置设置(例如 HTTP 通信设置、重试行为、服务区域端点设置,可能还包括任何与 SDK 相关的配置)提供一组默认值。使用此特征时,您可以获得针对常见使用场景量身定制的新配置默认值。

所有 Amazon SDK 的默认模式均已标准化。适用于 Java 的 SDK 2.x 支持以下默认模式:

  • legacy — 提供默认设置,这些设置因 SDK 而异,并且在建立 DefaultsMode 之前就已存在。

  • standard — 为大多数场景提供默认的非优化设置。

  • in-region — 基于标准模式构建,包括为从同一 Amazon Web Services 区域 内部调用 Amazon Web Services 的应用程序量身定制的设置。

  • cross-region — 基于标准模式构建,包括为调用其它 Amazon Web Services 区域中的 Amazon Web Services的应用程序量身定制的高超时设置。

  • mobile — 基于标准模式构建,包括为延迟较高的移动应用程序量身定制的高超时设置。

  • auto — 基于标准模式构建,包括实验特征。SDK 会尝试发现运行时系统环境以自动确定适当的设置。自动检测是基于启发式的,无法提供 100% 的准确性。如果无法确定运行时环境,则使用标准模式。自动检测可能会查询实例元数据和用户数据,这可能会带来延迟。如果启动延迟对您的应用程序而言至关重要,我们建议您改为选择显式 DefaultsMode 延迟。

您可以通过以下方式配置默认模式:

  • 直接通过 AwsClientBuilder.Builder#defaultsMode(DefaultsMode) 在客户端上配置。

  • 通过 defaults_mode 配置文件属性在配置文件上配置。

  • 通过 aws.defaultsMode 系统属性全局配置。

  • 通过 AWS_DEFAULTS_MODE 环境变量全局配置。

注意

对于除 legacy 之外的任何模式,随着最佳实践不断改进,提供的默认值可能会发生变化。因此,如果您使用的是 legacy 以外的模式,我们建议您在升级 SDK 时进行测试。

《Amazon SDK 和工具参考指南》中的智能配置默认值提供了不同默认模式下的配置属性及其默认值的列表。

您可以根据应用程序的特性以及与之互动的 Amazon Web Service选择默认模式值。

配置这些值时考虑了多种 Amazon Web Services。对于将 DynamoDB 表和应用程序部署在一个区域中的典型 DynamoDB 部署,in-region 默认模式在 standard 默认模式中最相关。

例 针对低延迟调用调整的示例 DynamoDB SDK 客户端配置

以下示例将预期的低延迟 DynamoDB 调用的超时调整为较低的值。

DynamoDbAsyncClient asyncClient = DynamoDbAsyncClient.builder() .defaultsMode(DefaultsMode.IN_REGION) .httpClientBuilder(AwsCrtAsyncHttpClient.builder()) .overrideConfiguration(ClientOverrideConfiguration.builder() .apiCallTimeout(Duration.ofSeconds(3)) .apiCallAttemptTimeout(Duration.ofMillis(500)) .build()) .build();

单独的 HTTP 客户端实现让您可以对超时和连接使用行为进行更精细的控制。例如,对于基于 Amazon CRT 的客户端,您可以启用 ConnectionHealthConfiguration,它允许基于 Amazon CRT 的客户端主动监控所用连接的运行状况。有关更多详细信息,请参阅Amazon SDK for Java 2.x 的文档

Keep-Alive 配置

启用 keep-alive 可以通过重复使用连接来减少延迟。有两种不同的 keep-alive:HTTP Keep-Alive 和 TCP Keep-Alive。

  • HTTP Keep-Alive 尝试维护客户端和服务器之间的 HTTPS 连接,以便以后的请求可以重复使用该连接。这会跳过对以后请求的重量级 HTTPS 身份验证。默认情况下,所有客户端都启用 HTTP Keep-Alive。

  • TCP Keep-Alive 请求底层操作系统通过套接字连接发送小数据包,以进一步保证套接字保持活动状态并立即检测任何丢包。这样可以确保以后的请求不会花时间尝试使用丢失的套接字。默认情况下,所有客户端都禁用 TCP Keep-Alive。您可能需要将其启用。以下代码示例说明如何对每个 HTTP 客户端执行此操作。当为所有不是基于 CRT 的 HTTP 客户端启用时,实际的 Keep-Alive 机制取决于操作系统。因此,您必须通过操作系统配置其它 TCP Keep-Alive 值,例如超时和数据包数量。您可以在 Linux 或 Mac 计算机上使用 sysctl 来执行此操作,也可以在 Windows 计算机上使用注册表值来执行此操作。

例 在基于 Apache 的 HTTP 客户端上启用 TCP Keep-Alive
DynamoDbClient client = DynamoDbClient.builder() .httpClientBuilder(ApacheHttpClient.builder().tcpKeepAlive(true)) .build();
基于 URLConnection 的 HTTP 客户端

任何使用基于 URLConnection 的 HTTP 客户端 HttpURLConnection 的同步客户端都没有启用 keep-alive 的机制

例 在基于 Netty 的 HTTP 客户端上启用 TCP Keep-Alive
DynamoDbAsyncClient client = DynamoDbAsyncClient.builder() .httpClientBuilder(NettyNioAsyncHttpClient.builder().tcpKeepAlive(true)) .build();
例 在基于 Amazon CRT 的 HTTP 客户端上启用 TCP Keep-Alive

对于基于 Amazon CRT 的 HTTP 客户端,您可以启用 TCP Keep-Alive 并控制持续时间。

DynamoDbClient client = DynamoDbClient.builder() .httpClientBuilder(AwsCrtHttpClient.builder() .tcpKeepAliveConfiguration(TcpKeepAliveConfiguration.builder() .keepAliveInterval(Duration.ofSeconds(50)) .keepAliveTimeout(Duration.ofSeconds(5)) .build())) .build();

使用异步 DynamoDB 客户端时,您可以启用 TCP Keep-Alive,如以下代码所示。

DynamoDbAsyncClient client = DynamoDbAsyncClient.builder() .httpClientBuilder(AwsCrtAsyncHttpClient.builder() .tcpKeepAliveConfiguration(TcpKeepAliveConfiguration.builder() .keepAliveInterval(Duration.ofSeconds(50)) .keepAliveTimeout(Duration.ofSeconds(5)) .build())) .build();

错误处理

在异常处理方面,Amazon SDK for Java 2.x 使用运行时(取消选中的)异常。

涵盖所有 SDK 异常的基本异常是 SdkServiceException,它扩展自 Java 取消选中的 RuntimeException。如果您捕获此异常,您就会捕获 SDK 引发的所有异常。

SdkServiceException 有一个名为 AwsServiceException 的子类。此子类表示与 Amazon Web Service通信时出现的任何问题。它有一个名为 DynamoDbException 的子类,这表示在与 DynamoDB 通信时出现问题。如果您捕获此异常,您就会捕获与 DynamoDB 相关的所有异常,但不会捕获其它 SDK 异常。

DynamoDbException 下有更具体的异常类型。其中一些异常类型适用于控制面板操作,例如 TableAlreadyExistsException。其它异常适用于数据面板操作。以下是常见的数据面板异常的示例:

  • ConditionalCheckFailedException—在请求中指定计算结果为 false 的条件。例如,您可能已尝试对项目执行有条件更新,但属性的实际值与条件预期值不匹配。将不会重试以这种方式失败的请求。

其它情况没有定义特定的异常。例如,当您的请求受到限制时,可能会引发特定 ProvisionedThroughputExceededException,而在其它情况下,会引发更通用的 DynamoDbException。无论哪种情况,您都可以通过检查 isThrottlingException() 是否返回 true 来确定异常是否由节流引起。

根据您的应用程序需求,您可以捕获所有 AwsServiceExceptionDynamoDbException 实例。但是通常您需要在不同的情况下采取不同的行为。与处理节流相比,处理条件检查失败的逻辑会有所不同。定义要处理的异常路径,并确保测试替代路径。这有助于确保您能够处理所有相关的场景。

DynamoDB 错误处理列出了您可能遇到的一些常见错误。您也可以参考常见错误来查看常见错误列表。对于特定的 API 调用,您还可以在其文档中找到可能的确切错误,例如查询 API。有关处理异常的信息,请参阅Amazon SDK for Java 2.x 的异常处理

Amazon 请求 ID

每个请求都包含一个请求 ID,如果您使用 Amazon Web Services Support 来诊断问题,则该 ID 会非常有用。派生自 SdkServiceException 的每个异常都有一个方法 requestId(),可用于检索请求 ID。

日志记录

使用 SDK 提供的日志记录既可以用于捕获客户端库中的任何重要消息,也可以用于进行更深入的调试。记录器是分层的,SDK 将 software.amazon.awssdk 用作其根记录器。可以使用TRACEDEBUGINFOWARNERRORALLOFF 中的一个来配置该级别。配置的级别将应用于该记录器并向下应用到记录器层次结构。

Amazon SDK for Java 2.x 利用适用于 Java 的 Simple Logging Facade(SLF4J)进行日志记录,它充当其它记录器周围的抽象层。这使您可以插入自己喜欢的记录器。有关插入记录器的说明,请参阅 SLF4J 用户手册

每个记录器都有特定的行为。默认情况下,Log4j 2.x 记录器会创建一个 ConsoleAppender,后者将日志事件附加到 System.out,并默认为 ERROR 日志级别。

SLF4J 输出中包含的 SimpleLogger 记录器默认为 System.err,并默认为 INFO 日志级别。

建议将任何生产部署的 software.amazon.awssdk 的级别设置为 WARN,以捕获来自 SDK 客户端库的任何重要消息,同时限制输出数量。

如果 SLF4J 在类路径上找不到支持的记录器(没有 SLF4J 绑定),则 SLF4J 将默认为无操作实现。此实现会导致将消息记录到 System.err,解释 SLF4J 在类路径上找不到记录器实现。为了防止出现这种情况,您需要添加记录器实现。为此,您可以在 Apache Maven pom.xml 中添加构建依赖项,例如 org.slf4j.slf4j-simpleorg.apache.logging.log4j.log4j-slf4j2-imp

Amazon SDK for Java 2.x 的文档说明了如何在 SDK 中配置日志记录,包括向应用程序配置添加日志记录依赖项。有关信息,请参阅使用适用于 Java 的 SDK 2.x 进行日志记录

Log4j2.xml 文件中的以下配置显示了在使用 Apache Log4j 2 记录器时如何调整日志记录行为。此配置将根记录器级别设置为 WARN。此日志级别将会被层次结构中的所有记录器(包括 software.amazon.awssdk 记录器)继承。

默认情况下,输出将转到 System.out。在以下示例中,我们仍然覆盖默认的输出 Log4j Appender 以应用定制的 Log4j PatternLayout

Log4j2.xml 配置文件示例

以下配置会将所有记录器层次结构的 ERRORWARN 级别的消息记录到控制台。

<Configuration status="WARN"> <Appenders> <Console name="ConsoleAppender" target="SYSTEM_OUT"> <PatternLayout pattern="%d{YYYY-MM-dd HH:mm:ss} [%t] %-5p %c:%L - %m%n" /> </Console> </Appenders> <Loggers> <Root level="WARN"> <AppenderRef ref="ConsoleAppender"/> </Root> </Loggers> </Configuration>

Amazon 请求 ID 日志记录

当出现问题时,您可以在异常中找到 RequestId。但是,如果您想要未生成异常的请求的 RequestId,则可以使用日志记录。

请求 ID 由 software.amazon.awssdk.request 记录器在 DEBUG 级别输出。以下示例扩展了前面的configuration example,将根记录器级别保持在 ERROR、将 software.amazon.awssdk 保持在级别 WARN,将 software.amazon.awssdk.request 保持在级别 DEBUG。设置这些级别有助于捕获请求 ID 和其它与请求相关的详细信息,例如端点和状态码。

<Configuration status="WARN"> <Appenders> <Console name="ConsoleAppender" target="SYSTEM_OUT"> <PatternLayout pattern="%d{YYYY-MM-dd HH:mm:ss} [%t] %-5p %c:%L - %m%n" /> </Console> </Appenders> <Loggers> <Root level="ERROR"> <AppenderRef ref="ConsoleAppender"/> </Root> <Logger name="software.amazon.awssdk" level="WARN" /> <Logger name="software.amazon.awssdk.request" level="DEBUG" /> </Loggers> </Configuration>

以下是日志输出的示例:

2022-09-23 16:02:08 [main] DEBUG software.amazon.awssdk.request:85 - Sending Request: DefaultSdkHttpFullRequest(httpMethod=POST, protocol=https, host=dynamodb.us-east-1.amazonaws.com, encodedPath=/, headers=[amz-sdk-invocation-id, Content-Length, Content-Type, User-Agent, X-Amz-Target], queryParameters=[]) 2022-09-23 16:02:08 [main] DEBUG software.amazon.awssdk.request:85 - Received successful response: 200, Request ID: QS9DUMME2NHEDH8TGT9N5V53OJVV4KQNSO5AEMVJF66Q9ASUAAJG, Extended Request ID: not available

分页

某些请求(例如查询扫描)会限制针对单个请求返回的数据大小,并要求您重复请求才能显示后续页面。

您可以使用 Limit 参数控制每页可读取的最大项目数。例如,您可以使用 Limit 参数仅检索最后 10 个项目。此限制是在应用任何筛选条件之前应从表中读取多少项目。筛选后无法确切指定想要检索 10 个项目。只有在切实检索到 10 个项目后,您才能控制预先筛选的数量并检查客户端。不管限制如何,每个响应始终有 1 MB 的最大大小。

API 响应中可能包含 LastEvaluatedKey,这表示响应因达到数量限制或大小限制而结束。此密钥是针对该响应评估的最后一个密钥。直接与 API 交互,您可以检索此 LastEvaluatedKey 并将其作为 ExclusiveStartKey 传递给后续调用,以便从该起点读取下一个数据块。如果未返回 LastEvaluatedKey,则表示没有其它与 QueryScan API 调用匹配的项目。

以下示例使用低级别接口根据 keyConditionExpression 参数将项目限制为 100。

QueryRequest.Builder queryRequestBuilder = QueryRequest.builder() .expressionAttributeValues(Map.of( ":pk_val", AttributeValue.fromS("123"), ":sk_val", AttributeValue.fromN("1000"))) .keyConditionExpression("pk = :pk_val AND sk > :sk_val") .limit(100) .tableName(TABLE_NAME); while (true) { QueryResponse queryResponse = DYNAMODB_CLIENT.query(queryRequestBuilder.build()); queryResponse.items().forEach(item -> { LOGGER.info("item PK: [" + item.get("pk") + "] and SK: [" + item.get("sk") + "]"); }); if (!queryResponse.hasLastEvaluatedKey()) { break; } queryRequestBuilder.exclusiveStartKey(queryResponse.lastEvaluatedKey()); }

Amazon SDK for Java 2.x 可通过提供自动分页方法(这些方法可进行多个服务调用以自动为您获取后续页面的结果)来简化与 DynamoDB 的交互。这简化了您的代码,但确实会消除对资源使用的一些控制,而手动读取页面可以保留这些控制。

通过使用 DynamoDB 客户端中可用的 Iterable 方法(例如 QueryPaginatorScanPaginator),SDK 可以负责分页。这些方法的返回类型是一个自定义的可迭代对象,您可以用它来遍历所有页面。SDK 将在内部为您处理服务调用。使用 Java Stream API 可以处理 QueryPaginator 的结果,如以下示例所示。

QueryPublisher queryPublisher = DYNAMODB_CLIENT.queryPaginator(QueryRequest.builder() .expressionAttributeValues(Map.of( ":pk_val", AttributeValue.fromS("123"), ":sk_val", AttributeValue.fromN("1000"))) .keyConditionExpression("pk = :pk_val AND sk > :sk_val") .limit(100) .tableName("YourTableName") .build()); queryPublisher.items().subscribe(item -> System.out.println(item.get("itemData"))).join();

数据类注释

Java SDK 提供了几个注释,您可以将这些注释放在数据类的属性上,这将影响 SDK 与它们的交互方式。通过添加注释,您可以让属性充当隐式原子计数器,维护自动生成的时间戳值,或跟踪项目版本号。有关更多信息,请参阅数据类注释