使用 AWS SDK for Java 2.x 对 DynamoDB 进行编程
本指南为想要将 Amazon DynamoDB 与 Java 结合使用的程序员提供了指导。本指南涵盖不同的概念,例如抽象层、配置管理、错误处理、控制重试策略和管理 Keep-Alive。
主题
关于 AWS SDK for Java 2.x
您可以使用官方的 AWS SDK for Java 从 Java 访问 DynamoDB。SDK for Java 有两个版本:1.x 和 2.x。我们已于 2024 年 1 月 12 日宣布
有关 AWS SDK 维护和支持的更多信息,请参阅《AWS SDK 和工具参考指南》中的 AWS SDK 和工具维护策略以及 AWS SDK 和工具版本支持矩阵。
AWS SDK for Java 2.x 是对 1.x 代码库的重大重写。适用于 Java 的 SDK 2.x 支持现代 Java 功能,例如 Java 8 中引入的非阻塞 I/O。适用于 Java 的 SDK 2.x 还增加了对可插拔 HTTP 客户端实现的支持,从而提高了网络连接灵活性,并提供更多配置选项。
从适用于 Java 的 SDK 1.x 到适用于 Java 的 SDK 2.x 的一个明显变化是使用了新的软件包名称。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
。
重要
Java 版本支持
AWS SDK for Java 2.x 为长期支持(LTS)Java 版本
开始使用 AWS SDK for Java 2.x
以下教程向您展示如何使用 Apache Maven
要完成本教程,您需要执行以下步骤:
步骤 1:为本教程进行设置
在开始本教程之前,您需要满足以下条件:
-
具有访问 Amazon DynamoDB 的权限。
-
具有 Java 开发环境,该环境配置为能够使用 AWS 访问门户以单点登录方式访问 AWS 服务
要进行本教程的设置,请按照《AWS SDK for Java 2.x 开发人员指南》中安装概述中的说明操作。在为 Java SDK 将开发环境配置为单点登录访问,并且 AWS 访问门户会话处于活动状态后,请继续本教程的步骤 2。
步骤 2:创建项目
要为本教程创建项目,您需要运行一条 Maven 命令,该命令会提示您输入有关如何配置项目的信息。完成所有输入并进行确认后,Maven 通过创建 pom.xml
文件并创建存根 Java 文件完成项目构建。
-
打开终端或命令提示符窗口,然后导航到您选择的目录,例如您的
Desktop
或Home
文件夹。 -
在终端输入以下命令,然后按 Enter。
mvn archetype:generate \ -DarchetypeGroupId=software.amazon.awssdk \ -DarchetypeArtifactId=archetype-app-quickstart \ -DarchetypeVersion=2.22.0
-
为每个提示输入第二列中列出的值。
提示 要输入的值 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>
-
输入最后一个值后,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
部分包含 AWS SDK for Java 2.x 的依赖项,而 dependencies
部分包含 DynamoDB 的依赖项。指定这些依赖项会强制 Maven 将相关的 .jar
文件包含到您的 Java 类路径中。默认情况下,AWS SDK 不包含所有 AWS 服务的所有类。对于 DynamoDB,如果您使用低级别接口,则应依赖 dynamodb
构件。或者,如果您使用高级别接口,则应依赖 dynamodb-enhanced
构件。如果您不包含相关依赖项,则无法编译您的代码。由于 maven.compiler.source
和 maven.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
类后,构建并运行该应用程序。
-
确保 AWS IAM Identity Center 会话处于活动状态。要进行确认,请运行 AWS Command Line Interface(AWS CLI)命令
aws sts get-caller-identity
并检查响应。如果您没有活动会话,请参阅使用 AWS CLI 登录了解相关说明。 -
打开终端或命令提示符窗口并导航至您的项目目录
getstarted
。 -
使用以下命令构件项目:
mvn clean package
-
使用以下命令运行应用程序:
mvn exec:java -Dexec.mainClass="org.example.App"
查看文件后,删除对象,然后删除存储桶。
成功
如果您的 Maven 项目生成和运行都没有错误,那么恭喜您!您已经使用适用于 Java 的 SDK 2.x. 成功构建了您的第一个 Java 应用程序。
清理
要清理您在本教程中创建的资源,请删除项目文件夹 getstarted
。
查看 AWS SDK for Java 2.x 文档
AWS SDK for Java 2.x 开发人员指南中涵盖了所有 AWS 服务中 SDK 的方方面面。建议您查看以下主题:
-
从版本 1.x 迁移到 2.x – 包括对 1.x 和 2.x 之间差异的详细说明。本主题还包含有关如何并行使用两个主要版本的说明。
-
适用于 Java 2.x SDK 的 DynamoDB 指南 – 向您展示如何执行基本的 DynamoDB 操作:创建表、操作项目和检索项目。这些示例均使用低级别接口。Java 有几个接口,如以下部分所述:支持的接口。
提示
阅读这些主题后,请将 AWS SDK for Java 2.x API 参考
支持的接口
AWS 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)"); } }
高级别接口
AWS SDK for Java 2.x 中的高级别接口称为 DynamoDB 增强型客户端。此接口提供了更为惯用的代码编写体验。
增强型客户端提供了一种在客户端数据类和专为存储该数据而设计的 DynamoDB 表之间进行映射的方法。您可以在代码中定义表与其相应模型类之间的关系。然后,您可以依靠 SDK 来管理数据类型操作。有关增强型客户端的更多信息,请参阅《AWS SDK for Java 2.x 开发人员指南》中的 DynamoDB 增强型客户端 API。
以下 PutItem 示例使用高级别接口。在此示例中,名为 YourItem
的 DynamoDbBean
创建了一个 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; } } }
AWS SDK for Java 1.x 有自己的高级别接口,通常由其主类 DynamoDBMapper
引用。AWS 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); } } }
使用不可变数据类和第三方样板生成库的高级别接口
上个示例中显示的不可变数据类需要一些样板代码。例如,Builder
类之外的数据类上的 getter 和 setter 逻辑。第三方库(例如 Project Lombok
以下示例展示了 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 和 AWS SDK 提供的以下注释:
-
@Builder
– 为 Project Lombok 提供的数据类生成复杂的生成器 API。 -
@DynamoDbImmutable
– 将 DynamoDbImmutable
类标识为 AWS SDK 提供的 DynamoDB 可映射实体注释。 -
@Value
– @Data
的不可变变体。默认情况下,所有字段均为私有和最终字段,并且不会生成 setter。Project Lombok 提供此注释。
文档接口
AWS SDK for Java 2.x 文档接口无需指定数据类型描述符。数据类型由数据本身的语义隐含。此文档接口与 AWS SDK for Java 1.x 的文档接口类似,但经过了重新设计。
下面的Document interface example显示了使用文档接口表达的 PutItem
调用。该示例还使用了 EnhancedDocument。要使用增强型文档 API 对 DynamoDB 表执行命令,必须先将该表与您的文档表架构相关联,以创建 DynamoDBTable
资源对象。Document 表架构生成器需要一个主索引键和一个或多个属性转换器提供程序。
您可以使用 AttributeConverterProvider.defaultProvider()
转换默认类型的文档属性。您可以使用自定义 AttributeConverterProvider
实现来更改整体默认行为。您还可以更改单个属性的转换器。AWS 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 数据类型之间相互转换,可以使用以下实用程序方法:
-
EnhancedDocument.fromJson(String json)
– 通过 JSON 字符串创建新的 EnhancedDocument 实例。 -
EnhancedDocument.toJson()
– 创建文档的 JSON 字符串表示形式,以便在应用程序中像使用任何其他 JSON 对象一样使用它。
将接口与 Query
示例进行比较
本部分展示了使用各种接口表达的相同 Query
调用。要微调这些查询的结果,请注意以下几点:
-
DynamoDB 将针对一个特定的分区键值,因此必须完全指定分区键。
-
要使查询仅针对购物车商品,排序键必须有一个使用
begins_with
的键条件表达式。 -
我们使用
limit()
将查询限制为最多 100 个返回项。 -
我们将
scanIndexForward
设置为 false。结果按照 UTF-8 字节的顺序返回,这通常意味着首先返回编号最小的购物车商品。通过将scanIndexForward
设置为 false,我们可以颠倒顺序,首先返回编号最大的购物车商品。 -
我们会应用筛选条件来删除任何不符合条件的结果。无论商品是否与筛选条件匹配,筛选的数据都会消耗读取容量。
例 使用低级别接口的 Query
以下示例使用 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")); } }
例 使用文档接口的 Query
以下示例使用文档接口查询名为 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")); } }
例 使用高级别接口的 Query
以下示例使用 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; } } }
使用不可变数据类的高级别接口
使用高级别不可变数据类执行 Query
时,除了实体类 YourItem
或 YourImmutableItem
的构造之外,代码与高级别接口示例相同。有关更多信息,请参阅 PutItem 示例。
使用不可变数据类和第三方样板生成库的高级别接口
使用高级别不可变数据类执行 Query
时,除了实体类 YourItem
或 YourImmutableLombokItem
的构造之外,代码与高级别接口示例相同。有关更多信息,请参阅 PutItem 示例。
其他代码示例
有关如何将 DynamoDB 与适用于 Java 的 SDK 2.x 结合使用的其他示例,请参阅以下代码示例存储库:
同步和异步编程
AWS SDK for Java 2.x 为 DynamoDB 等 AWS 服务提供了同步和异步客户端。
DynamoDbClient
和 DynamoDbEnhancedClient
类提供了同步方法,这些方法会阻止执行您的线程,直到客户端接收到服务的响应。如果您不需要异步操作,则此客户端是与 DynamoDB 交互的最直接方式。
DynamoDbAsyncClient
和 DynamoDbEnhancedAsyncClient
类提供了异步方法,这些方法会立即返回,并控制调用的线程,而不必等待响应。非阻塞客户端的优势在于,它允许在几个线程之间实现高并发性,从而以最少的计算资源高效处理 I/O 请求。这会提高吞吐量和响应能力。
AWS SDK for Java 2.x 使用对非阻塞 I/O 的本机支持。AWS SDK for Java 1.x 必须模拟非阻塞 I/O。
由于同步方法在收到响应之前返回,所以需要通过某种方法在响应准备就绪时接收响应。AWS SDK for Java 中的异步方法会返回 CompletableFuture
CompletableFuture
对象上调用 get()
或 join()
时,您的代码将阻塞,直到结果可用。如果您在发出请求的同时进行此调用,则其行为与普通的同步调用类似。
有关异步编程的更多信息,请参阅《AWS SDK for Java 2.x 开发人员指南》中的使用异步编程。
HTTP 客户端
为了支持每个客户端,有一个处理与 AWS 服务的通信的 HTTP 客户端。您可以插入备用 HTTP 客户端,选择一个具有最适合您应用程序的特性的客户端。有些更轻量;有些则有更多的配置选项。
有些 HTTP 客户端仅支持同步使用,而另一些则仅支持异步使用。有关可帮助您为工作负载选择最佳 HTTP 客户端的流程图,请参阅《AWS SDK for Java 2.x 开发人员指南》中的 HTTP 客户端建议。
以下列表列出了一些可能的 HTTP 客户端:
基于 Apache 的 HTTP 客户端
ApacheHttpClient
ApacheHttpClient
类的信息,请参阅《AWS SDK for Java 2.x 开发人员指南》中的配置基于 Apache 的 HTTP 客户端。
基于 URLConnection
的 HTTP 客户端
UrlConnectionHttpClient
UrlConnectionHttpClient
类的信息,请参阅《AWS SDK for Java 2.x 开发人员指南》中的配置基于 URLConnection 的 HTTP 客户端。
基于 Netty 的 HTTP 客户端
NettyNioAsyncHttpClient
类支持异步客户端。这是实现异步使用的默认选择。有关配置 NettyNioAsyncHttpClient
类的信息,请参阅《AWS SDK for Java 2.x 开发人员指南》中的配置基于 Netty 的 HTTP 客户端。
基于 AWS CRT 的 HTTP 客户端
AWS 公共运行时(CRT)库中的较新 AwsCrtHttpClient
和 AwsCrtAsyncHttpClient
类提供了更多支持同步和异步客户端的选项。与其它 HTTP 客户端相比,AWS CRT 可提供:
-
更快的 SDK 启动时间
-
更小的内存占用空间
-
缩短了延迟时间
-
连接运行状况管理
-
DNS 负载均衡
有关配置 AwsCrtHttpClient
和 AwsCrtAsyncHttpClient
类的信息,请参阅《AWS SDK for Java 2.x 开发人员指南》中的配置基于 AWS CRT 的 HTTP 客户端。
基于 AWS CRT 的 HTTP 客户端不是默认选项,因为它会破坏现有应用程序的向后兼容性。但是,对于 DynamoDB,不管是同步使用还是异步使用,都建议使用基于 AWS CRT 的 HTTP 客户端。
有关基于 AWS CRT 的 HTTP 客户端的介绍,请参阅 AWS 开发人员工具博客中的宣布在 AWS SDK for Java 2.x 中推出 AWS CRT HTTP 客户端
配置 HTTP 客户端
配置客户端时,您可以提供各种配置选项,包括:
您可以通过向服务客户端 Builder
类提供 ClientOverrideConfiguration
ClientOverrideConfiguration
提供了标准配置选项。不同的可插拔 HTTP 客户端也有实现特定的配置可能性。
超时配置
您可以调整客户端配置,来控制与服务调用相关的各种超时。与其他 AWS 服务相比,DynamoDB 的延迟更低。因此,您可能需要将这些属性调整为较低的超时值,以便在出现网络问题时可以快速失效。
您可以在 DynamoDB 客户端上使用 ClientOverrideConfiguration
或通过更改底层 HTTP 客户端实现的详细配置选项来自定义与延迟相关的行为。
您可以使用 ClientOverrideConfiguration
配置以下有影响力的属性:
-
apiCallAttemptTimeout
– 在放弃和超时之前,等待单次 HTTP 请求尝试完成的时间。 -
apiCallTimeout
– 客户端完全执行 API 调用所需的时间。这包括由所有 HTTP 请求(包括重试)组成的请求处理程序执行。
AWS SDK for Java 2.x 为某些超时选项(例如连接超时和套接字超时等)提供了默认值ClientOverrideConfiguration
中设置这些超时值,SDK 将使用套接字超时值,而不是整体 API 调用超时值。套接字超时的默认值为 30 秒。
RetryMode
您应该考虑的另一个与超时配置相关的配置是 RetryMode
配置对象。此配置对象包含一组重试行为。
适用于 Java 的 SDK 2.x 支持以下重试模式:
-
legacy
– 默认重试模式,如果您未明确更改。这种重试模式特定于 Java SDK。它的特点是最多重试 3 次,对于 DynamoDB 等服务来说,重试次数更多,最多为 8 次。 -
standard
– 之所以命名为“标准”,是因为它与其他 AWS SDK 更加一致。对于首次重试,此模式随机等待 0 毫秒到 1000 毫秒不等的时间。如果需要再次重试,此模式会从 0 毫秒到 1000 毫秒之间随机选择另一个时间,然后将其乘以二。如果需要更多重试,它会进行相同的随机选择,然后乘以 4,依此类推。每次等待的上限为 20 秒。与legacy
模式相比,此模式会对检测到的更多故障条件执行重试。对于 DynamoDB,除非您使用 numRetries 进行覆盖,否则它最多总共执行三次尝试。 -
adaptive
– 基于standard
模式构建,动态限制 AWS 请求速率以最大限度提高成功率。这样做可能会以牺牲请求延迟为代价。当可预测的延迟很重要时,不建议使用自适应重试模式。
您可以在《AWS SDK 和工具参考指南》的重试行为主题中找到这些重试模式的扩展定义。
重试策略
所有 RetryMode
配置都有 RetryPolicy
RetryCondition
TokenBucketRetryCondition
TokenBucket
中减去令牌。
当客户端遇到可重试错误(例如节流异常或临时服务器错误)时,SDK 将自动重试请求。您可以控制这些重试发生的次数和频率。
配置客户端时,您可以提供支持以下参数的 RetryPolicy
:
-
numRetries
– 在认为请求失败之前应当应用的最多重试次数。无论您使用哪种重试模式,默认值都是 8。警告
请务必在适当考虑后更改此默认值。
-
backoffStrategy
– 将BackoffStrategy
应用于重试, FullJitterBackoffStrategy
成为默认策略。此策略根据当前的重试次数、基本延迟和最大回退时间,在额外重试之间执行指数延迟。然后它会添加抖动以提供一点随机性。无论重试模式如何,指数延迟中使用的基本延迟均为 25 毫秒。 -
retryCondition
–RetryCondition
决定是否完全重试请求。默认情况下,它将重试一组它认为可以重试的特定 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
通常通过指定 DefaultsMode
来隐式配置 ClientOverrideConfiguration
和 RetryMode
不管理的超时属性。
AWS SDK for Java 2.x(2.17.102 或更高版本)引入了对 DefaultsMode
的支持。此功能为常见的可配置设置(例如 HTTP 通信设置、重试行为、服务区域端点设置,可能还包括任何与 SDK 相关的配置)提供一组默认值。使用此功能时,您可以获得针对常见使用场景量身定制的新配置默认值。
所有 AWS SDK 的默认模式均已标准化。适用于 Java 的 SDK 2.x 支持以下默认模式:
-
legacy
– 提供默认设置,这些设置因 AWS SDK 而异,并且在建立DefaultsMode
之前就已存在。 -
standard
– 为大多数场景提供默认的非优化设置。 -
in-region
– 基于标准模式构建,包括为从同一 AWS 区域 内部调用 AWS 服务 的应用程序量身定制的设置。 -
cross-region
– 基于标准模式构建,包括为调用不同区域中 AWS 服务的应用程序量身定制的高超时设置。 -
mobile
– 基于标准模式构建,包括为延迟较高的移动应用程序量身定制的高超时设置。 -
auto
– 基于标准模式构建,包括实验功能。SDK 会尝试发现运行时系统环境以自动确定适当的设置。自动检测是基于启发式的,无法提供 100% 的准确性。如果无法确定运行时环境,则使用标准模式。自动检测可能会查询实例元数据和用户数据,这可能会带来延迟。如果启动延迟对您的应用程序而言至关重要,建议您改为选择显式DefaultsMode
延迟。
您可以通过以下方式配置默认模式:
-
直接通过
AwsClientBuilder.Builder#defaultsMode(DefaultsMode)
在客户端上配置。 -
通过
defaults_mode
配置文件属性在配置文件上配置。 -
通过
aws.defaultsMode
系统属性进行全局配置。 -
通过
AWS_DEFAULTS_MODE
环境变量进行全局配置。
注意
对于除 legacy
之外的任何模式,随着最佳实践不断改进,提供的默认值可能会发生变化。因此,如果您使用的是 legacy
以外的模式,建议您在升级 SDK 时进行测试。
《AWS SDK 和工具参考指南》中的智能配置默认值提供了不同默认模式下的配置属性及其默认值的列表。
您可以根据应用程序的特性以及与之互动的 AWS 服务选择默认模式值。
配置这些值时应考虑广泛的 AWS 服务选择。对于将 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 客户端实现让您可以对超时和连接使用行为进行更精细的控制。例如,对于基于 AWS CRT 的客户端,可以启用 ConnectionHealthConfiguration
,以便该客户端能够主动监控所用连接的运行状况。有关更多信息,请参阅《AWS SDK for Java 2.x 开发人员指南》中的基于 AWS CRT 的 HTTP 客户端的高级配置。
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
例 在基于 Netty 的 HTTP 客户端上启用 TCP Keep-Alive
DynamoDbAsyncClient client = DynamoDbAsyncClient.builder() .httpClientBuilder(NettyNioAsyncHttpClient.builder().tcpKeepAlive(true)) .build();
例 在基于 AWS CRT 的 HTTP 客户端上启用 TCP Keep-Alive
对于基于 AWS 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();
错误处理
在异常处理方面,AWS SDK for Java 2.x 使用运行时(未经核查的)异常。
涵盖所有 SDK 异常的基本异常是 SdkServiceException
RuntimeException
。如果您捕获此异常,就会捕获 SDK 引发的所有异常。
SdkServiceException
有一个名为 AwsServiceException
DynamoDbException
DynamoDbException
下有更具体的异常类型TableAlreadyExistsException
-
ConditionalCheckFailedException
– 在请求中指定计算结果为 false 的条件。例如,您可能已尝试对项目执行有条件更新,但属性的实际值与条件预期值不匹配。不会重试以这种方式失败的请求。
其他情况没有定义特定的异常。例如,当您的请求受到限制时,可能会引发特定 ProvisionedThroughputExceededException
,而在其他情况下,会引发更通用的 DynamoDbException
。无论哪种情况,都可以通过检查 isThrottlingException()
是否返回 true
来确定异常是否由节流引起。
根据您的应用程序需求,您可以捕获所有 AwsServiceException
或 DynamoDbException
实例。但是,您通常需要在不同的情况下采取不同的行为。处理条件检查失败的逻辑与处理节流不同。定义要处理的异常路径,并确保测试替代路径。这有助于确保您能够处理所有相关场景。
有关您可能遇到的常见错误的列表,请参阅 DynamoDB 错误处理。另请参阅《Amazon DynamoDB API 参考》中的常见错误。API 参考还提供每个 API 操作(例如 Query
操作)可能发生的确切错误。有关处理异常的信息,请参阅《AWS SDK for Java 2.x 开发人员指南》中的AWS SDK for Java 2.x 异常处理。
AWS 请求 ID
每个请求都包含一个请求 ID,如果您正在与 AWS Support 部门合作诊断问题,则该 ID 会非常有用。派生自 SdkServiceException
的每个异常都有一个可用于检索请求 ID 的 requestId()
日志记录
使用 SDK 提供的日志记录既可以捕获客户端库中的任何重要消息,也可以帮助您进行更深入的调试。记录器是分层的,SDK 将 software.amazon.awssdk
用作其根记录器。可以使用 TRACE
、DEBUG
、INFO
、WARN
、ERROR
、ALL
或 OFF
中的一个设置来配置记录器级别。所配置的级别将应用于该记录器并向下应用到记录器层次结构。
AWS SDK for Java 2.x 使用 Simple Logging Façade for Java(SLF4J)进行日志记录。这充当其他记录器周围的抽象层,您可以用它来插入自己喜欢的记录器。有关插入记录器的说明,请参阅 SLF4J 用户手册
每个记录器都有特定的行为。默认情况下,Log4j 2.x 记录器会创建一个 ConsoleAppender
,后者将日志事件附加到 System.out
,并默认处于 ERROR
日志级别。
SLF4J 输出中包含的 SimpleLogger 记录器默认为 System.err
,并默认处于 INFO
日志级别。
建议将任何生产部署的 software.amazon.awssdk
的级别设置为 WARN
,以捕获来自 SDK 客户端库的任何重要消息,同时限制输出数量。
如果 SLF4J 在类路径上找不到支持的记录器(没有 SLF4J 绑定),它将默认为无操作实现System.err
,解释 SLF4J 在类路径上找不到记录器实现。为了防止出现这种情况,您必须添加记录器实现。为此,您可以在 Apache Maven pom.xml
中添加构件依赖项,例如 org.slf4j.slf4j-simple
或 org.apache.logging.log4j.log4j-slf4j2-imp
。
有关如何在 SDK 中配置日志记录,包括向应用程序配置中添加日志记录依赖项的信息,请参阅《AWS SDK for Java 开发人员指南》中的适用于 Java 的 SDK 2.x 日志记录。
Log4j2.xml
文件中的以下配置显示了在使用 Apache Log4j 2 记录器时如何调整日志记录行为。此配置将根记录器级别设置为 WARN
。层次结构中的所有记录器(包括 software.amazon.awssdk
记录器)将继承此日志级别。
默认情况下,输出将转到 System.out
。在以下示例中,我们仍然覆盖默认的输出 Log4j Appender 以应用定制的 Log4j PatternLayout
。
Log4j2.xml
配置文件示例
以下配置在所有日志记录器层次结构的 ERROR
和 WARN
级别,将消息记录到控制台。
<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>
AWS 请求 ID 日志记录
当出现问题时,您可以在异常中找到请求 ID。但是,如果您想要未生成异常的请求的请求 ID,可以使用日志记录。
software.amazon.awssdk.request
记录器在 DEBUG
级别输出请求 ID。以下示例扩展了前面的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
分页
某些请求(例如 Query
和 Scan
)会限制针对单个请求返回的数据大小,并要求您重复请求才能显示后续页面。
您可以使用 Limit
参数控制每页可读取的最大项目数。例如,您可以使用 Limit
参数仅检索最后 10 个项目。此限制指定在应用任何筛选条件之前应从表中读取多少项目。如果您希望筛选后正好有 10 个项目,则没有办法指定。只有在实际检索到 10 个项目后,才能控制预先筛选的数量并检查客户端。不管限制如何,每个响应最多允许 1 MB 的大小。
LastEvaluatedKey
可能包含在 API 响应中。这表示响应因达到数量限制或大小限制而结束。此密钥是针对该响应评估的最后一个密钥。通过直接与 API 交互,您可以检索此 LastEvaluatedKey
并将其作为 ExclusiveStartKey
传递给后续调用,以便从该起点读取下一个数据块。如果未返回 LastEvaluatedKey
,则表示没有更多与 Query
或 Scan
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()); }
AWS SDK for Java 2.x 可通过提供自动分页方法(这些方法可进行多个服务调用以自动为您获取后续页面的结果)来简化与 DynamoDB 的交互。这简化了您的代码,但会消除对资源使用的一些控制,而手动读取页面可以保持这些控制。
通过使用 DynamoDB 客户端中提供的 Iterable
方法(例如 QueryPaginator
ScanPaginator
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 与属性的交互方式。通过添加注释,您可以让属性充当隐式原子计数器,维护自动生成的时间戳值,或跟踪项目版本号。有关更多信息,请参阅数据类注释。