AWS SDK for Java 2.x を使用した DynamoDB のプログラミング - Amazon DynamoDB

AWS SDK for Java 2.x を使用した DynamoDB のプログラミング

このプログラミングガイドは、Java で Amazon DynamoDB を使用することを検討しているプログラマーを対象としています。このガイドでは、抽象化レイヤー、設定管理、エラー処理、再試行ポリシーの制御、キープアライブの管理など、さまざまな概念について説明します。

AWS SDK for Java 2.x について

DynamoDB には、公式の AWS SDK for Java を使用して Java からアクセスできます。SDK for Java には、1.x と 2.x の 2 つのバージョンがあります。1.x のサポート終了は、2024 年 1 月 12 日に発表されました。1.x は、2024 年 7 月 31 日にメンテナンスモードとなり、サポートは 2025 年 12 月 31 日に終了する予定です。新規開発には、2018 年に最初にリリースされた 2.x を使用することを強くお勧めします。このガイドは 2.x のみを対象としており、SDK の DynamoDB との関連性がある部分のみを重点的に取り扱っています。

AWS SDK のメンテナンスとサポートの詳細については、「AWS SDKs and Tools Reference Guide」の「AWS SDK and Tools maintenance policy」 および 「AWS SDKs and Tools version support matrix」を参照してください。

AWS SDK for Java 2.x は、1.x コードベースより大幅に変更されています。SDK for Java 2.x は、Java 8 で導入されたノンブロッキング I/O などの最新の Java 機能をサポートしています。また、SDK for Java 2.x では、ネットワーク接続の柔軟性と構成可能性を高めるために、プラガブル HTTP クライアント実装のサポートも追加されています。

SDK for Java 1.x から SDK for Java 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 を使用します。

重要

AWS SDK for Java 1.x には、com.amazonaws.dynamodbv2 という名前の DynamoDB パッケージがあります。パッケージ名の「v2」は、Java 2 (J2SE) 用であることを示すものではありません。代わりに「v2」は、パッケージが低レベル API の元のバージョンではなく、DynamoDB 低レベル API の 2 番目のバージョンをサポートしていることを示します。

Java バージョンのサポート

AWS SDK for Java 2.x は、Java リリースの長期サポート (LTS) を完全にサポートします。

AWS SDK for Java 2.x の開始方法

次のチュートリアルでは、SDK for Java 2.x の依存関係を定義するために Apache Maven を使用する方法を説明しています。このチュートリアルでは、DynamoDB に接続して使用可能な DynamoDB テーブルを一覧表示するコードの記述方法も説明します。このガイドのチュートリアルは、「AWS SDK for Java 2.x デベロッパーガイド」の「Get started with the AWS SDK for Java 2.x」のチュートリアルに基づいています。このチュートリアルは、Amazon S3 の代わりに DynamoDB を呼び出すように編集されています。

ステップ 1: このチュートリアルのために設定する

このチュートリアルを開始する前に、以下を実行する必要があります

  • DynamoDB へのアクセス許可。

  • AWS のサービス を使用して AWS アクセスポータル にアクセスするように設定された Java 開発環境。

このチュートリアル用にセットアップするには、「AWS SDK for Java 2.x デベロッパーガイド」の「Setup overview」の手順に従う必要があります。Java SDK のシングルサインオンアクセスを使用して開発環境を設定し、アクティブな AWS アクセスポータルセッションを確立した後、このチュートリアルのステップ 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. 各プロンプトで、2 列目に記載されている値を入力します。

    プロンプト 入力する値
    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 セクションには AWS SDK for Java 2.x への依存関係が含まれており、dependencies セクションには DynamoDB に対する依存関係があります。このような依存関係を指定すると、Maven は関連する .jar ファイルを Java クラスパスに強制的に含めます。デフォルトでは、AWS SDK にはすべての AWS のサービス クラスは含まれません。DynamoDB で低レベルインターフェイスを使用する場合、dynamodb アーティファクトへの依存関係が必要です。高レベルインターフェイスを使用する場合は、dynamodb-enhanced アーティファクトへの依存関係が必要です。関連する依存関係が含まれていない場合、コードはコンパイルされません。プロジェクトでは Java 1.8 が使用されています。これは、maven.compiler.sourcemaven.compiler.target およびプロパティに 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 クライアントのインスタンスを使用します。これは、どの HTTP クライアントを使用するかを Maven が求めたときに 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 クラスにはプログラムのメインロジックが含まれています。Handler のインスタンスが App クラス内で作成されると、DependencyFactoryDynamoDbClient サービスクライアントを配置します。コードでは、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インスタンスを使用して既存のテーブルのリストを取得します。特定のアカウントと AWS リージョン にテーブルが配置されている場合、コードは 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. アクティブな AWS IAM Identity Center セッションがあることを確認します。確認するには、AWS Command Line Interface (AWS CLI) コマンド aws sts get-caller-identity を実行してレスポンスを確認します。アクティブなセッションがない場合は、「Sign in using the AWS CLI」の手順を参照してください。

  2. ターミナルまたはコマンドプロンプトウィンドウを開いて、プロジェクトディレクトリ getstarted に移動します。

  3. 次のコマンドを使用して、プロジェクトを構築します。

    mvn clean package
  4. 次のコマンドを使用して、アプリケーションを実行します。

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

ファイルを表示した後、オブジェクトを削除し、その後にバケットを削除します。

成功

Maven プロジェクトがエラーなしで構築および実行された場合は、正常に完了しています! これで、Java 2.x 用 SDK を使用して最初の Java アプリケーションを正常に構築できました。

クリーンアップ

このチュートリアルで作成したリソースをクリーンアップするには、プロジェクトフォルダ getstarted を削除します。

AWS SDK for Java 2.x ドキュメントのレビュー

https://docs.aws.amazon.com/sdk-for-java/latest/developer-guide/home.htmlAWS SDK for Java 2.x デベロッパーガイド」は、すべての AWS のサービス サービスにおける SDK のすべての側面について説明しています。以下のトピックを確認することをお勧めします。

  • Migrate from version 1.x to 2.x — 1.x と 2.x の違いについて詳細に説明しています。このトピックでは、両方のメジャーバージョンを並行して使用する方法も説明しています。

  • DynamoDB guide for Java 2.x SDK — テーブルの作成、項目の操作、項目の取得など、基本的な DynamoDB のオペレーションを実行する方法を説明しています。上記の例では、低レベルインターフェイスを使用しています。「サポートされているインターフェイス」セクションで説明するとおり、Java には複数のインターフェイスがあります。

ヒント

これらのトピックを確認したら、「AWS SDK for Java 2.x API Reference」をブックマークします。このガイドはすべての AWS のサービス を対象としており、主な API リファレンスとして使用することをお勧めします。

サポートされているインターフェイス

AWS SDK for Java 2.x は、必要な抽象化レベルに応じて、以下のインターフェイスをサポートします。

低レベルインターフェイス

低レベルインターフェイスは、基盤となるサービス API に 1 対 1 でマッピングされています。すべての 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 enhanced client API」を参照してください。

次の PutItem の例では、高レベルインターフェイスを使用してしています。この例では、YourItem という名前の DynamoDbBeanTableSchema を作成して、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 のマッピング機能は、イミュータブルデータクラスで動作します。不変クラスにはゲッターしかなく、SDK がクラスのインスタンスを作成するために使用するビルダークラスが必要です。Java のイミュータブルクラスは、開発者が副作用のないクラスを作成するために使用できる一般的なスタイルです。これらのクラスの動作は、複雑なマルチスレッドアプリケーションでより予測可能になります。「High-level interface example」で説明したとおり、@DynamoDbBean アノテーションを使用する代わりに、イミュータブルクラスは Builder クラスを入力として受け取る @DynamoDbImmutable アノテーションを使用します。

次の例では、DynamoDbEnhancedClientImmutablePutItem Builder クラスを入力としてテーブルスキーマを作成します。この例では次に、スキーマを 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 などのサードパーティーライブラリは、このようなタイプのボイラープレートコードの生成に役立ちます。ボイラープレートコードの大部分を低減すると、イミュータブルデータクラスと AWS SDK の使用に必要なコードの量を制限できます。これにより、コードの生産性と可読性がさらに向上します。詳細については、「AWS SDK for Java 2.x デベロッパーガイド」の「Use third-party libraries, such as 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 が提供するデータクラス用の複雑な Builder API を生成します。

  • @DynamoDbImmutableDynamoDbImmutable クラスを、AWS SDK が提供する DynamoDB マッピング可能なエンティティのアノテーションとして識別します。

  • @Value@Data のイミュータブルバリアント。デフォルトで、すべてのフィールドはプライベートかつ最終的なものになり、setters は生成されません。Project Lombok はこのアノテーションを提供します。

Document インターフェイス

AWS SDK for Java 2.x Document インターフェイスでは、データ型記述子を指定する必要がありません。データ型は、データ自体のセマンティクスによって暗示されています。この Document インターフェイスは AWS SDK for Java 1.x の Document インターフェイスに似ていますが、インターフェイスは再設計されています。

次の Document interface example は、この Document インターフェイスを使用した PutItem コールの表現を示しています。この例では EnhancedDocument も使用しています。Enhanced Document API を使用して DynamoDB テーブルに対してコマンドを実行するには、まずテーブルを Document テーブルスキーマに関連付けて、DynamoDBTable リソースオブジェクトを作成する必要があります。Document テーブルスキーマの Builder には、プライマリインデックスキーと属性コンバーターのプロバイダーが必要です。

デフォルトタイプの Document 属性の変換には AttributeConverterProvider.defaultProvider() を使用できます。全体的なデフォルト動作は、カスタム AttributeConverterProvider 実装で変更できます。また、1 つの属性のコンバーターを変更することもできます。「AWS SDK および ツールリファレンスガイド」には、カスタムコンバーターの使用方法の詳細と例が提供されています。コンバーターは主に、デフォルトコンバーターがないドメインクラスの属性に使用します。カスタムコンバータを使用すると、DynamoDB への書き込みまたは読み取りに必要な情報を SDK に提供できます。

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() — Document の JSON 文字列表現を作成して、その他の JSON オブジェクトと同様にアプリケーションで使用できるようにします。

Query 例を使用したインターフェイスの比較

このセクションでは、さまざまなインターフェイスにおける、同じ Query 呼び出しの表現について説明します。これらのクエリの結果を微調整する場合は、以下の点に注意してください。

  • DynamoDB では、単一の特定のパーティションキー値をターゲットにするため、パーティションキーは完全に指定する必要があります。

  • ソートキーには begins_with を使用したキー条件式があるため、カートの商品のみがこのクエリの対象となります。

  • 返される項目を最大 100 個に制限するために、クエリでは limit() を使用します。

  • 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")); } }
例 Document インターフェイスを使用した Query

次の例では、Document インターフェイスを使用して、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 Enhanced Client 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」の例を参照してください。

その他のコード例

SDK for Java 2.x で DynamoDB を使用する場合のその他の例については、以下のコードサンプルリポジトリを参照してください。

同期プログラミングと非同期プログラミング

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 デベロッパーガイド」の「Use asynchronous programming」を参照してください。

HTTP クライアント

すべてのクライアントをサポートするための AWS のサービス との通信を処理する HTTP クライアントがあります。アプリケーションに最も適切な特性を持つ HTTP クライアントを選択して、代替 HTTP クライアントをプラグインできます。比較的軽量なクライアントも、設定オプションが豊富にあるクライアントもあります。

HTTP クライアントによっては、同期使用のみをサポートするものもあれば、非同期使用のみをサポートするものもあります。ワークロードに最適な HTTP クライアントの選択に役立つフローチャートについては、「AWS SDK for Java 2.x デベロッパーガイド」の「HTTP client recommendations」を参照してください。

利用できる HTTP クライアントのいくつかを次の一覧にまとめています。

Apache ベースの HTTP クライアント

ApacheHttpClient クラスは同期サービスクライアントをサポートします。これは同期でのデフォルトの HTTP クライアントです。ApacheHttpClient クラスの設定については、「AWS SDK for Java 2.x デベロッパーガイド」の「Configure the Apache-based HTTP client」を参照してください。

URLConnection ベースの HTTP クライアント

UrlConnectionHttpClient クラスは、同期クライアント向けのもう 1 つのオプションです。Apache ベースの HTTP クライアントよりもロード時間がかからないとはいえ、機能は限られています。UrlConnectionHttpClient クラスの設定については、「AWS SDK for Java 2.x デベロッパーガイド」の「Configure the URLConnection-based HTTP client」を参照してください。

Netty ベースの HTTP クライアント

NettyNioAsyncHttpClient クラスは非同期クライアントをサポートします。これは非同期でのデフォルトの選択肢です。NettyNioAsyncHttpClient クラスの設定については、「AWS SDK for Java 2.x デベロッパーガイド」の「Configure the Netty-based HTTP client」を参照してください。

AWS CRT ベースの HTTP クライアント

同期クライアントと非同期クライアントの両方をサポートするオプションには、AWS Common Runtime (CRT) ライブラリに新たに追加された AwsCrtHttpClient クラスと AwsCrtAsyncHttpClient クラスがあります。他の HTTP クライアントと比較して、AWS CRT は以下を提供します。

  • SDK 起動時間の短縮

  • より小さなメモリフットプリント

  • レイテンシータイムの短縮

  • 接続のヘルス管理

  • DNS ロードバランサー

AwsCrtHttpClient クラスと AwsCrtAsyncHttpClient クラスの設定については、「AWS SDK for Java 2.x デベロッパーガイド」の「Configure the AWS CRT-based HTTP clients」を参照してください。

AWS CRT ベースの HTTP クライアントは、既存のアプリケーションとの下位互換性が失われるため、デフォルトではありません。ただし、DynamoDB の場合は、同期と非同期の両方に AWS CRT ベースの HTTP クライアントを使用することをお勧めします。

AWS CRT ベースの HTTP クライアントの概要については、「AWS Developer Tools Blog」の「Announcing availability of the AWS CRT HTTP Client in the AWS SDK for Java 2.x」を参照してください。

HTTP クライアントの設定

クライアントを設定する際には、次のとおりのさまざまな設定オプションを指定できます。

  • API コールのさまざまな側面でのタイムアウトの設定。

  • TCP キープアライブの有効化。

  • エラー発生時の再試行ポリシーの制御。

  • 実行インターセプターインスタンスが変更できる実行属性の指定。実行インターセプターは、API リクエストと応答の実行をインターセプトするコードを作成できます。これにより、メトリクスの公開やリクエストの変更などのタスクを進行中に実行できます。

  • HTTP ヘッダーの追加や操作。

  • クライアント側のパフォーマンスメトリクスの追跡を有効にできます。この機能を使用すると、アプリケーション内のサービスクライアントに関するメトリクスを収集し、Amazon CloudWatch で出力を分析できます。

  • 非同期の再試行やタイムアウトタスクなどのタスクのスケジュール設定に使用する代替エグゼキュータサービスを指定できます。

設定を制御するには、ClientOverrideConfiguration オブジェクトをサービスクライアントの Builder クラスに提供します。これについては、次のセクションのコードサンプルのいくつかで説明します。

ClientOverrideConfiguration では、標準的な設定のオプションが提供されます。プラガブル HTTP クライアントによっては、実装に応じた設定もできます。

タイムアウト設定

クライアントの設定を調整して、サービスの呼びだしに関連するさまざまなタイムアウトを制御できます。DynamoDB は、その他の AWS のサービス サービスと比較してレイテンシーが低減します。このため、ネットワークに問題が発生した場合のフェイルファストを実現するように、プロパティを調整してタイムアウト値を低く指定することをお勧めします。

DynamoDB クライアントで ClientOverrideConfiguration を使用するか、基盤となる HTTP クライアント実装で詳細な設定オプションを変更することによって、レイテンシーに関する動作をカスタマイズできます。

以下の影響を左右するプロパティを設定するには、ClientOverrideConfiguration を使用できます。

  • apiCallAttemptTimeout — HTTP リクエストの 1 回の試行が完了するまで、諦めてタイムアウトになるまで待機する時間。

  • apiCallTimeout – クライアントで API コールを完了するまでに許容される時間。これには、再試行を含むすべての HTTP リクエストで設定されるリクエストハンドラーの実行が含まれます。

接続タイムアウトやソケットタイムアウトなど、タイムアウトオプションによっては、AWS SDK for Java 2.x は、デフォルト値を提供します。SDK は、API コールのタイムアウトや個別の API コールの試行タイムアウトのデフォルト値は提供していません。このようなタイムアウトが ClientOverrideConfiguration で設定されていない場合、SDK は全体的な API コールタイムアウトの代わりにソケットのタイムアウト値を使用します。ソケットのタイムアウトのデフォルト値は 30 秒です。

RetryMode

タイムアウト設定に関して考慮すべきもう 1 つの設定は、RetryMode 設定オブジェクトです。この設定オブジェクトには、再試行動作のコレクションが含まれています。

SDK for Java 2.x は、次の再試行モードをサポートしています。

  • legacy — 明示的に変更しない場合のデフォルトの再試行モード。この再試行モードは Java SDK 固有のものです。このモードでは、最大 3 回まで再試行でき、DynamoDB などのサービスの場合は最大 8 回まで再試行できます。

  • standard — 他の AWS SDK との整合性が高いことから、「standard」と名付けられています。このモードでは、最初の再試行で 0 ミリ秒から 1,000 ミリ秒の範囲のランダムな期間だけ待機します。もう一度再試行が必要な場合は、このモードは 0 ミリ秒から 1,000 ミリ秒までの別のランダムな期間を選択して、これを 2 倍します。さらに再試行が必要な場合は、このモードは同じようにランダムな時間を選択し、これに 4 を掛け、試行を続けます。各待機時間の上限は 20 秒です。このモードは、検出された障害条件の数が legacy モードよりも多い場合に再試行を実行します。DynamoDB の場合、numRetries でオーバーライドしない限り、最大合計 3 回の試行が実行されます。

  • adaptivestandard モードに基づいて構築されています。成功率を最大化するために AWS リクエスト率を動的に制限します。これにより、リクエストのレイテンシーが増大する場合があります。予測可能なレイテンシーが重要な場合は、アダプティブ再試行モードはお勧めしません。

このような再試行モードの詳細な定義については、「AWS SDK とツールのリファレンスガイド」の「再試行動作」トピックを参照してください。

再試行ポリシー

すべての RetryMode 設定には、単一または複数の RetryCondition 設定に基づいて構築される RetryPolicy があります。TokenBucketRetryCondition は、DynamoDB SDK クライアント実装の再試行動作には、特に重要です。この条件は、トークンバケットアルゴリズムを使用して SDK が行う再試行の数を制限します。選択した再試行モードに応じて、スロットリング例外によって TokenBucket からトークンが減算される場合と減算されない場合があります。

クライアントでスロットリング例外や一時的なサーバーエラーなどの再試行できるエラーが発生すると、SDK は自動的にリクエストを再試行します。このような再試行の回数と頻度は制御できます。

クライアントを設定する際、以下のパラメータをサポートする RetryPolicy を指定できます。

  • numRetries — リクエストが失敗したとみなされるまでに適用すべき最大再試行回数。使用する再試行モードを問わず、デフォルト値は 8 です。

    警告

    このデフォルト値は、慎重に考慮した上で変更してください。

  • backoffStrategy - 再試行に適用する BackoffStrategy。デフォルト戦略は FullJitterBackoffStrategy です。この戦略は、現在の再試行回数、ベースとなる遅延、最大バックオフ時間に基づいて、追加の再試行の間に指数関数的な遅延を実行します。次に、ジッターを追加して、多少のランダム性を提供します。指数関数的遅延で使用される基本遅延は、再試行モードを問わず 25 ミリ秒です。

  • retryConditionRetryCondition は、リクエストを再試行するかどうかを決定します。デフォルトでは、再試行可能と思われる特定の HTTP ステータスコードと例外のセットを再試行します。ほとんどの場合、このデフォルトの設定で十分です。

次のコードは代替再試行ポリシーを提供します。合計で 5 回の再試行 (合計 6 回のリクエスト) を指定します。最初の再試行は約 100 ミリ秒の遅延の後に実行され、追加の再試行ごとにその時間が指数関数的に倍増され、最大 1 秒の遅延が発生します。

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

ClientOverrideConfigurationRetryMode が管理しないタイムアウトプロパティは、通常、DefaultsMode を指定することで明示的に設定されます。

AWS SDK for Java 2.x (バージョン 2.17.102 以降) では、DefaultsMode のサポートが導入されました。この機能は、HTTP 通信設定、再試行動作、サービスのリージョンエンドポイント設定、他の SDK 関連の構成など、一般的で設定可能な設定のデフォルト値のセットを提供します。この機能を使用すると、一般的な使用シナリオに合わせて新しい設定のデフォルトを指定できます。

デフォルトモードはすべての AWS SDK で標準化されています。SDK for Java 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 デプロイの場合、standard デフォルトモードの中で最も関連性があるのは in-region のデフォルトモードです。

例 低レイテンシーの呼び出し向けに調整した 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 デベロッパーガイド」の「Advanced configuration of AWS CRT-based HTTP clients」を参照してください。

キープアライブの設定

キープアライブを有効にすると、接続を再利用することでレイテンシーを短縮できます。キープアライブには、HTTP キープアライブと TCP キープアライブの 2 種類があります。

  • HTTP キープアライブは、その後のリクエストで接続を再利用できるように、クライアントとサーバー間の HTTPS 接続の維持を試行します。これにより、重い HTTPS 認証がその後のリクエストでスキップされます。HTTP キープアライブはすべてのクライアントでデフォルトで有効になっています。

  • TCP キープアライブは、基盤となるオペレーティングシステムに対して、ソケット接続を介して小型のパケットを送信するようにリクエストします。これにより、ソケットが維持されることがさらに保証され、ドロップを即座に検出できます。これにより、ドロップしたソケットの使用にその後のリクエストが時間を費やす必要がなくなります。TCP キープアライブは、デフォルトではすべてのクライアントで無効になっています。以下のコードサンプルは、各 HTTP クライアントでこれを有効化する方法を示しています。CRT ベース以外のすべての HTTP クライアントの場合、キープアライブを有効にすると、実際のキープアライブメカニズムはオペレーティングシステムに依存します。このため、タイムアウトやパケット数など、追加の TCP キープアライブ値をオペレーティングシステムで設定する必要があります。これを実行するには、Linux または macOS では sysctl を使用し、Windows ではレジストリ値を使用します。

例 Apache ベースの HTTP クライアントで TCP キープアライブを有効にするには
DynamoDbClient client = DynamoDbClient.builder() .httpClientBuilder(ApacheHttpClient.builder().tcpKeepAlive(true)) .build();
URLConnection ベースの HTTP クライアント

URLConnection ベースの HTTP クライアント HttpURLConnection を使用する同期クライアントには、キープアライブを有効にするメカニズムはありません。

例 Netty ベースの HTTP クライアントで TCP キープアライブを有効にするには
DynamoDbAsyncClient client = DynamoDbAsyncClient.builder() .httpClientBuilder(NettyNioAsyncHttpClient.builder().tcpKeepAlive(true)) .build();
例 AWS CRT ベースの HTTP クライアントで TCP キープアライブを有効にするには

AWS CRT ベースの HTTP クライアントを使用すると、TCP キープアライブを有効にして、実行時間を制御できます。

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

非同期の DynamoDB クライアントを使用する場合は、次のコードのとおり TCP キープアライブを有効にすることができます。

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 例外をカバーする基本となる例外は、Java 非チェック RuntimeException の拡張である SdkServiceException です。これをキャッチすると、SDK からスローされるすべての例外をキャッチできます。

SdkServiceException には、AwsServiceException というサブクラスがあります。このサブクラスは、AWS のサービス との間の通信に問題があることを示します。これには、DynamoDB との通信の問題を示す DynamoDbException というサブクラスがあります。これをキャッチすると、DynamoDB に関連するすべての例外はキャッチできますが、他の SDK 例外はキャッチできません。

DynamoDbException には、さらに具体的な例外タイプもあります。このような例外タイプの中には、TableAlreadyExistsException などのコントロールプレーンの操作に適用されるものもあります。データプレーン操作に適用されるものもあります。一般的なデータプレーン例外の例は、以下のとおりです。

  • ConditionalCheckFailedException — リクエストに false と評価される条件を指定しました。たとえば、項目に条件付き更新を実行しようとしたかもしれませんが、属性の実際の値は、条件の予期される値と一致しませんでした。この方法で失敗したリクエストは再試行されません。

その他の状況については、特定の例外は定義されていません。例えば、リクエストがスロットリングすると、特定の ProvisionedThroughputExceededException がスローされ、その他の場合にはより一般的な DynamoDbException がスローされる可能性があります。いずれの場合も、isThrottlingException()true を返したかを確認することで、例外がスロットリングによって引き起こされたかどうかを判断できます。

アプリケーションのニーズに応じて、すべての AwsServiceException インスタンスまたは DynamoDbException インスタンスをキャッチできます。ただし多くの場合、状況に応じて異なる動作が必要になります。条件チェックの失敗を処理するロジックは、スロットリングを処理するロジックとは異なります。どの例外パスに対処するかを定義して、代替パスは必ずテストしてください。テストの実施は、関連するすべてのシナリオに確実に対処できるようにするうえで役立ちます。

よく発生するエラーのリストについては、「DynamoDB でのエラー処理」を参照してください。また、「Amazon DynamoDB API Reference」の「Common Errors」も参照してください。この API Reference には、Query オペレーションなど、各 API オペレーションで発生する可能性のあるエラーも記載されています。例外処理の詳細については、「AWS SDK for Java 2.x デベロッパーガイド」の「Exception handling for the AWS SDK for Java 2.x」を参照してください。

AWS リクエスト ID

各リクエストにはリクエスト ID が含まれており、AWS Support と連携して問題を診断する場合に取得すると便利です。SdkServiceException から派生した各例外には、リクエスト ID を取得できる requestId() メソッドがあります。

ログ記録

SDK が提供するログ記録を使用すると、クライアントライブラリから重要なメッセージをキャッチする場合や、より詳細なデバッグを行う場合の両方で役に立ちます。ロガーは階層化されており、SDK はルートロガーとして software.amazon.awssdk を使用します。レベルは、TRACEDEBUGINFOWARNERRORALL、または OFF のいずれかを使用して設定できます。設定したレベルは、そのロガーとロガー階層の下位レベルに適用されます。

AWS SDK for Java 2.x は、ログ記録に Simple Logging Façade for Java (SLF4J) を使用します。これは他のロガーの抽象化レイヤーとして機能し、これを使用して希望するロガーをプラグインできます。ロガーの接続方法については、「SLF4J ユーザーマニュアル」を参照してください。

各ロガーには特定の動作があります。Log4j 2.x ロガーは、デフォルトで、ログイベントを System.out に追加する ConsoleAppender を作成し、デフォルトで ERROR ログレベルを設定します。

SLF4J に含まれる SimpleLogger ロガーは、デフォルトで System.err を出力し、ログレベルはデフォルトで INFO になります。

出力量を制限しながら、SDK のクライアントライブラリから重要なメッセージをキャッチできるように、本番環境のデプロイでは software.amazon.awssdk のレベルを WARN に設定することをお勧めします。

SLF4J がクラスパス上でサポートされているロガーを見つけられない場合 (SLF4J バインディングがない場合)、SLF4J はデフォルトで no operation implementation になります。この実装により、SLF4J がクラスパス上でロガー実装を見つけられなかったことを説明するメッセージが System.err に記録されます。このような状況を防ぐには、ロガー実装を追加する必要があります。これを実行するには、Apache Maven の pom.xmlorg.slf4j.slf4j-simpleorg.apache.logging.log4j.log4j-slf4j2-imp などのアーティファクトに対する依存関係を追加します。

アプリケーション構成へのログ記録依存関係の追加など、SDK でのログ記録の構成方法については、「AWS SDK for Java デベロッパーガイド」の「Logging with the SDK for Java 2.x」を参照してください。

Log4j2.xml ファイルの次の設定では、Apache Log4j 2 ロガーを使用している場合にログ記録の動作を調整する方法を示しています。この設定では、ルートロガーレベルは WARN に設定しています。このログレベルは、software.amazon.awssdk ロガーなど、階層内のすべてのロガーに継承されます。

デフォルトでは、出力は System.out に送信されます。次の例では、デフォルトの出力 Log4j アペンダを上書きして、カスタマイズした 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 が必要な場合は、ログ記録を使用できます。

リクエスト ID は、DEBUG ロガーによって software.amazon.awssdk.request レベルで出力されます。次の例は、前の configuration example を拡張して、ルートロガーのレベルを ERRORsoftware.amazon.awssdk のレベルを WARNsoftware.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

ページ分割

QueryScan など、リクエストによっては、1 回のリクエストで返されるデータのサイズが制限され、後続のページを取得するにはリクエストを繰り返し行う必要がある場合があります。

各ページで読み取られる項目の最大数は、Limit パラメータを使用して制御できます。Limit パラメータを使用すると、例えば、最後の 10 項目のみを取得することができます。この制限は、フィルタリングが適用される前にテーブルから読み込む項目の上限数を指定します。フィルタリングの結果を 10 項目に正確に指定する方法はありません。事前にフィルタリング済みの数を制御し、実際に 10 項目を取得した場合にのみクライアント側でチェックできます。制限にかかわらず、すべての応答の最大サイズは常に 1 MB です。

API レスポンスには LastEvaluatedKey が含まれる場合があります。これは、制限数または制限サイズに達したためにレスポンスが終了したことを示します。このキーは、応答のために評価された最後のキーです。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 の操作を簡素化します。これによりコードが簡素化されます。ただし、ページを手動で読み取ることで維持していたリソース使用量がある程度制御できなくなります。

QueryPaginatorScanPaginator など、DynamoDB クライアントで利用可能な Iterable メソッドを使用すると、SDK がページ分割を処理します。このようなメソッドの戻り値の型はカスタム iterable で、これを使用してすべてのページを反復処理できます。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 が属性を処理する方法に影響します。アノテーションを追加すると、属性を暗黙的なアトミックカウンタとして動作させたり、自動生成したタイムスタンプ値を維持したり、項目のバージョン番号を追跡したりできます。詳細については、「データクラスのアノテーション」を参照してください。