

翻訳は機械翻訳により提供されています。提供された翻訳内容と英語版の間で齟齬、不一致または矛盾がある場合、英語版が優先します。

# の使用 DynamoDB
<a name="examples-dynamodb"></a>

[DynamoDB](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Introduction.html) は、高速で予測可能なパフォーマンスとシームレスなスケーラビリティを特長とするフルマネージド NoSQL データベースサービスです。このセクションでは、 AWS SDK for Java 2.x を使用して DynamoDB を使用する方法について説明します。

## DynamoDB クライアントの選択
<a name="ddb-clients-overview"></a>

SDK には、DynamoDB を使用するための 2 つの主なアプローチがあります。

低レベルクライアント (`DynamoDbClient`)  
DynamoDB オペレーションへの直接アクセスを提供し、リクエストとレスポンスを完全に制御します。詳細な制御が必要な場合や動的スキーマを使用する場合は、このクライアントを使用します。

拡張クライアント (`DynamoDbEnhancedClient`)  
Java オブジェクトと DynamoDB 項目間の自動マッピングによるオブジェクト指向プログラミングを提供します。また、固定スキーマに従わない JSON のようなデータを使用するための、ドキュメント指向の機能も提供します。明確に定義されたデータモデルまたはドキュメントタイプのデータを使用する場合は、このクライアントを使用します。

## DynamoDB クライアントの設定
<a name="ddb-configuration-setup"></a>

DynamoDB を使用する前に、最適なパフォーマンスと信頼性を実現するようにクライアントを設定します。

### DynamoDB の再試行動作について
<a name="ddb-retry-behavior"></a>

DynamoDB クライアントは、デフォルトの最大再試行回数である 8 を使用します。これは他の AWS のサービス クライアントよりも多い回数です。この再試行回数の多さは、DynamoDB の分散性および一時的な容量制限に対処するために役立ちます。再試行戦略についての詳細は、「[で再試行動作を設定する AWS SDK for Java 2.x](retry-strategy.md)」を参照してください。

### アカウントベースのエンドポイントでのパフォーマンス最適化
<a name="ddb-account-based-endpoints-v2"></a>

DynamoDB は[AWS 、アカウント ID を使用してリクエストのルーティングを合理化することで、パフォーマンスを向上させるアカウントベースのエンドポイント](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Programming.SDKOverview.html#Programming.SDKs.endpoints)を提供します。 AWS 

この機能を使用するには、 AWS SDK for Java 2.xのバージョン 2.28.4 以降が必要です。[Maven Central リポジトリ](https://central.sonatype.com/artifact/software.amazon.awssdk/bom/versions)で最新バージョンを検索できます。サポートされている SDK バージョンでは、新しいエンドポイントが自動的に使用されます。

アカウントベースのルーティングをオプトアウトするには、次のいずれかのオプションを選択します。
+ `AccountIdEndpointMode` を `DISABLED` に設定して DynamoDB サービスクライアントを設定する。
+ 環境変数を設定する。
+ JVM システムプロパティを設定する。
+ 共有 AWS 設定ファイル設定を更新します。

次の例は、DynamoDB サービスクライアントを設定してアカウントベースのルーティングを無効にする方法を示しています。

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

その他の設定オプションの詳細については、「SDK およびツールリファレンスガイド」の[「アカウントベースのエンドポイント](https://docs.aws.amazon.com/sdkref/latest/guide/feature-account-endpoints.html)」を参照してください。 AWS SDKs 

## このトピックで扱う内容
<a name="ddb-topics-overview"></a>

以下のセクションでは、DynamoDB を使用する方法を示します。
+ [DynamoDB のテーブルの操作](examples-dynamodb-tables.md) - テーブルの作成、記述、更新、削除
+ [で項目を操作する DynamoDB](examples-dynamodb-items.md) - 個々の項目の追加、取得、更新
+ [を使用して Java オブジェクトを DynamoDB 項目にマッピングする AWS SDK for Java 2.x](dynamodb-enhanced-client.md) - 拡張クライアントでのオブジェクトマッピングとドキュメント指向データの使用

その他の DynamoDB コード例については、[「 コード例ライブラリ」のDynamoDB ](java_dynamodb_code_examples.md) AWS コード例」を参照してください。

# DynamoDB のテーブルの操作
<a name="examples-dynamodb-tables"></a>

テーブルは、DynamoDB データベースのすべての項目のコンテナです。DynamoDB のデータの追加または削除を行う前に、テーブルを作成する必要があります。

テーブルごとに、以下を定義する必要があります。
+ アカウントおよびリージョンに一意であるテーブル*名*。
+ *プライマリキー*。すべての値は一意でなければならず、テーブル内のどの 2 つの項目も同じプライマリキー値を持つことはできません。

  プライマリキーは、単一のパーティション (ハッシュ) キーで構成される*シンプル*なプライマリキーにするか、パーティションとソート (範囲) キーで構成される*複合*プライマリキーにすることができます。

  各キーバリューには、[https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/ScalarAttributeType.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/ScalarAttributeType.html) クラスによって列挙される*データ型*が関連付けられています。キー値は、バイナリ (B)、数値 (N)、または文字列 (S) になります。詳細については、Amazon DynamoDB デベロッパーガイドの[命名規則とデータ型](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html)を参照してください。
+  テーブル用に予約された読み込み/書き込みキャパシティーユニットの数を定義する*プロビジョンドスループット*の値。
**注記**  
 [Amazon DynamoDB の料金](https://aws.amazon.com/dynamodb/pricing/)は、テーブルに設定したプロビジョニングされたスループット値に基づくため、テーブルに必要と予想される分だけの容量を予約します。

  テーブルのプロビジョンドスループットはいつでも変更できるため、必要に応じてキャパシティーを調整できます。

## テーブルの作成
<a name="dynamodb-create-table"></a>

新しい DynamoDB テーブルを作成するには、`DynamoDbClient’s` `createTable` メソッドを使用します。テーブルのプライマリキーを識別するために使用する、テーブル属性とテーブルスキーマを構築する必要があります。また、最初のプロビジョニングされたスループット値およびテーブル名を指定する必要があります。

**注記**  
選択した名前のテーブルがすでに存在している場合は、`[DynamoDbException](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/DynamoDbException.html)` がスローされます。

### シンプルプライマリキーを使用してテーブルを作成する
<a name="dynamodb-create-table-simple"></a>

このコードは、テーブルのシンプルプライマリキーである 1 つの属性を持つテーブルを作成します。この例では、`[AttributeDefinition](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/AttributeDefinition.html)` と `[KeySchemaElement](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/KeySchemaElement.html)` オブジェクトを [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/CreateTableRequest.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/CreateTableRequest.html) に使用しています。

 **インポート** 

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

 **Code** 

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

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

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

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

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

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

[GitHub](https://github.com/awsdocs/aws-doc-sdk-examples/blob/0b1785e42949ebf959eaa0f0da4dc2a48f92ea25/javav2/example_code/dynamodb/src/main/java/com/example/dynamodb/CreateTable.java) で完全な例をご覧ください。

### 複合プライマリキーを使用してテーブルを作成する
<a name="dynamodb-create-table-composite"></a>

次の例では、2 つの属性を持つテーブルを作成します。どちらの属性も複合プライマリキーに使用されます。

 **インポート** 

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

 **Code** 

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

       String tableId = "";

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

[GitHub](https://github.com/awsdocs/aws-doc-sdk-examples/blob/0b1785e42949ebf959eaa0f0da4dc2a48f92ea25/javav2/example_code/dynamodb/src/main/java/com/example/dynamodb/CreateTableCompositeKey.java) で完全な例をご覧ください。

## テーブルの一覧表示
<a name="dynamodb-list-tables"></a>

特定のリージョンのテーブルを一覧表示するには、`DynamoDbClient’s` `listTables` メソッドを呼び出します。

**注記**  
指定したテーブルがアカウントやリージョンにない場合は、[https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/ResourceNotFoundException.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/ResourceNotFoundException.html) がスローされます。

 **インポート** 

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

 **Code** 

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

        boolean moreTables = true;
        String lastName = null;

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

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

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

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

デフォルトでは、1 回の呼び出しで最大 100 個のテーブルが返ります。評価された最後のテーブルを取得するには、返された [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/ListTablesResponse.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/ListTablesResponse.html) オブジェクトに対して `lastEvaluatedTableName` を使用します。この値を使用して、前回の一覧表示で返された最後の値以降から、一覧表示を開始できます。

[GitHub](https://github.com/awsdocs/aws-doc-sdk-examples/blob/0b1785e42949ebf959eaa0f0da4dc2a48f92ea25/javav2/example_code/dynamodb/src/main/java/com/example/dynamodb/ListTables.java) で完全な例をご覧ください。

## テーブルの説明 (テーブルに関する情報の取得)
<a name="dynamodb-describe-table"></a>

`DynamoDbClient’s` `describeTable` メソッドを使用して、テーブルに関する情報を取得します。

**注記**  
指定したテーブルがアカウントやリージョンにない場合は、[https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/ResourceNotFoundException.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/ResourceNotFoundException.html) がスローされます。

 **インポート** 

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

 **Code** 

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

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

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

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

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

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

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

[GitHub](https://github.com/awsdocs/aws-doc-sdk-examples/blob/0b1785e42949ebf959eaa0f0da4dc2a48f92ea25/javav2/example_code/dynamodb/src/main/java/com/example/dynamodb/DescribeTable.java) で完全な例をご覧ください。

## テーブルの変更 (更新)
<a name="dynamodb-update-table"></a>

テーブルのプロビジョンドスループット値は、`DynamoDbClient’s` `updateTable` メソッドを呼び出すことで随時変更できます。

**注記**  
指定したテーブルがアカウントやリージョンにない場合は、[ResourceNotFoundException](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/ResourceNotFoundException.html) がスローされます。

 **インポート** 

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

 **Code** 

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

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

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

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

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

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

[GitHub](https://github.com/awsdocs/aws-doc-sdk-examples/blob/0b1785e42949ebf959eaa0f0da4dc2a48f92ea25/javav2/example_code/dynamodb/src/main/java/com/example/dynamodb/UpdateTable.java) で完全な例をご覧ください。

## テーブルの削除
<a name="dynamodb-delete-table"></a>

テーブルを削除するには、`DynamoDbClient’s` `deleteTable` メソッドを呼び出してテーブルの名前を指定します。

**注記**  
指定したテーブルがアカウントやリージョンにない場合は、[ResourceNotFoundException](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/ResourceNotFoundException.html) がスローされます。

 **インポート** 

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

 **Code** 

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

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

        try {
            ddb.deleteTable(request);

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

[GitHub](https://github.com/awsdocs/aws-doc-sdk-examples/blob/0b1785e42949ebf959eaa0f0da4dc2a48f92ea25/javav2/example_code/dynamodb/src/main/java/com/example/dynamodb/DeleteTable.java) で完全な例をご覧ください。

## 詳細情報
<a name="more-information"></a>
+  Amazon DynamoDB デベロッパーガイドの[テーブルの操作のガイドライン](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/GuidelinesForTables.html)
+  Amazon DynamoDB デベロッパーガイドの [DynamoDB のテーブルの操作](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/WorkingWithTables.html)

# で項目を操作する DynamoDB
<a name="examples-dynamodb-items"></a>

では DynamoDB、項目は*属性*のコレクションであり、それぞれに*名前*と*値*があります。属性値はスカラー型、セット型、ドキュメント型のいずれかです。詳細については、 Amazon DynamoDB デベロッパーガイドの[命名規則とデータ型](https://docs.aws.amazon.com//amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html)を参照してください。

## テーブルからの項目の取り出し (取得)
<a name="dynamodb-get-item"></a>

DynamoDbClient の `getItem` メソッドを呼び出して、指定する項目のテーブル名とプライマリキーバリューを持つ [GetItemRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/GetItemRequest.html) オブジェクトを渡します。これにより、その項目のすべての属性を含む [GetItemResponse](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/GetItemResponse.html) オブジェクトが返されます。特定の属性を取得するには、[ で、1 つ以上の](https://docs.aws.amazon.com//amazondynamodb/latest/developerguide/Expressions.ProjectionExpressions.html)プロジェクション式`GetItemRequest`を指定します。

返された `GetItemResponse` オブジェクトの `item()` メソッドを使用して、項目に関連付けられているキー (String) と値 ([AttributeValue](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/AttributeValue.html)) のペアの [Map](https://docs.oracle.com/javase/8/docs/api/index.html?java/util/Map.html) を取得できます。

 **インポート** 

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

 **Code** 

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

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

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

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

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

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

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

[GitHub](https://github.com/awsdocs/aws-doc-sdk-examples/blob/bc964a243276990f05c180618ea8b34777c68f0e/javav2/example_code/dynamodb/src/main/java/com/example/dynamodb/GetItem.java) で完全な例をご覧ください。

## 非同期クライアントを使用したテーブルからの項目の取り出し (取得)
<a name="id1ddb"></a>

DynamoDbAsyncClient の `getItem` メソッドを呼び出して、指定する項目のテーブル名とプライマリキーバリューを持つ [GetItemRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/GetItemRequest.html) オブジェクトを渡します。

その項目のすべての属性を持つ [Collection](https://docs.oracle.com/javase/8/docs/api/index.html?java/util/Collection.html) インスタンスを返すことができます (次の例を参照)。

 **インポート** 

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

 **Code** 

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

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

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

        try {

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

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

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

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

[GitHub](https://github.com/awsdocs/aws-doc-sdk-examples/blob/bc964a243276990f05c180618ea8b34777c68f0e/javav2/example_code/dynamodbasync/src/main/java/com/example/dynamodbasync/DynamoDBAsyncGetItem.java) で完全な例をご覧ください。

## テーブルへの新しい項目の追加
<a name="dynamodb-add-item"></a>

項目の属性を表すキーと値のペアの[マップ](https://docs.oracle.com/javase/8/docs/api/index.html?java/util/Map.html)を作成します。これらには、テーブルのプライマリキーフィールドの値を含める必要があります。プライマリキーで特定される項目がすでにある場合、フィールドはリクエストによって*更新*されます。

**注記**  
指定したテーブルがアカウントやリージョンにない場合は、[ResourceNotFoundException](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/ResourceNotFoundException.html) がスローされます。

 **インポート** 

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

 **Code** 

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

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

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

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

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

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

[GitHub](https://github.com/awsdocs/aws-doc-sdk-examples/blob/f4eaf2b2971805cfb2b87a8e5ab408f83169432e/javav2/example_code/dynamodb/src/main/java/com/example/dynamodb/PutItem.java) で完全な例をご覧ください。

## テーブルの既存の項目の更新
<a name="dynamodb-update-item"></a>

テーブルに既に存在する項目の属性を更新するには、DynamoDbClient の `updateItem` メソッドを呼び出して、テーブル名、プライマリキーバリュー、更新するフィールドのマップを渡します。

**注記**  
指定したテーブルがアカウントやリージョンにない場合、または渡したプライマリキーで特定される項目がない場合、[ResourceNotFoundException](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/ResourceNotFoundException.html) がスローされます。

 **インポート** 

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

 **Code** 

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

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

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

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

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

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

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

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

[GitHub](https://github.com/awsdocs/aws-doc-sdk-examples/blob/f4eaf2b2971805cfb2b87a8e5ab408f83169432e/javav2/example_code/dynamodb/src/main/java/com/example/dynamodb/UpdateItem.java) で完全な例をご覧ください。

## テーブルの既存の項目の削除
<a name="dynamodb-delete-item"></a>

テーブルに存在する項目を削除するには、DynamoDbClient の `deleteItem` メソッドを使用して、テーブル名とプライマリキーバリューを渡します。

**注記**  
指定したテーブルがアカウントやリージョンにない場合、または渡したプライマリキーで特定される項目がない場合、[ResourceNotFoundException](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/ResourceNotFoundException.html) がスローされます。

 **インポート** 

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

 **Code** 

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

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

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

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

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

[GitHub](https://github.com/awsdocs/aws-doc-sdk-examples/blob/f4eaf2b2971805cfb2b87a8e5ab408f83169432e/javav2/example_code/dynamodb/src/main/java/com/example/dynamodb/DeleteItem.java) で完全な例をご覧ください。

## 詳細情報
<a name="more-information"></a>
+  Amazon DynamoDB デベロッパーガイドの[項目の操作のガイドライン](https://docs.aws.amazon.com//amazondynamodb/latest/developerguide/best-practices.html)
+  Amazon DynamoDB デベロッパーガイドの[「 でのアイテムの操作 DynamoDB](https://docs.aws.amazon.com//amazondynamodb/latest/developerguide/WorkingWithItems.html)」

# を使用して Java オブジェクトを DynamoDB 項目にマッピングする AWS SDK for Java 2.x
<a name="dynamodb-enhanced-client"></a>

[DynamoDB Enhanced Client API](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/package-summary.html) は、SDK for Java v1.x の `DynamoDBMapper` クラスの後継となる高レベルのライブラリです。クライアント側のクラスを DynamoDB テーブルにマッピングする簡単な方法を提供します。テーブルおよび対応するデータクラスの間の関係をコードで定義します。関係を定義したら、DynamoDB のテーブルまたは項目に対して、さまざまな作成、読み取り、更新、または削除 (CRUD) オペレーションを直感的に実行できます。

DynamoDB Enhanced Client API には、定義されたスキーマに従わないドキュメントタイプの項目を操作できる[拡張ドキュメント API](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/document/package-summary.html) も含まれています。

**Topics**
+ [DynamoDB Enhanced Client API の使用を開始する](ddb-en-client-getting-started.md)
+ [DynamoDB Enhanced Client API の基礎について学ぶ](ddb-en-client-use.md)
+ [高度なマッピング機能を使用する](ddb-en-client-adv-features.md)
+ [DynamoDB 用の拡張ドキュメント API を使用して JSON ドキュメントを操作する](ddb-en-client-doc-api.md)
+ [拡張機能を使用して DynamoDB 拡張クライアントオペレーションをカスタマイズする](ddb-en-client-extensions.md)
+ [DynamoDB 拡張クライアント API を非同期的に使用する](ddb-en-client-async.md)
+ [データクラス注釈](ddb-en-client-anno-index.md)

# DynamoDB Enhanced Client API の使用を開始する
<a name="ddb-en-client-getting-started"></a>

次のチュートリアルでは、DynamoDB Enhanced Client API を操作するために必要な基本事項を紹介します。

## 依存関係を追加する
<a name="ddb-en-client-gs-dep"></a>

プロジェクトで DynamoDB Enhanced Client API を使い始めるには、`dynamodb-enhanced` Maven アーティファクトへの依存関係を追加します。これは次の例で示されます。

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

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

Maven central リポジトリで[最新バージョン](https://central.sonatype.com/artifact/software.amazon.awssdk/bom)を検索し、*<VERSION>* をこの値に置き換えます。

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

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

Maven central リポジトリで[最新バージョン](https://central.sonatype.com/artifact/software.amazon.awssdk/bom)を検索し、*<VERSION>* をこの値に置き換えます。

------

# データクラスから `TableSchema` を生成する
<a name="ddb-en-client-gs-tableschema"></a>

`[TableSchema](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/TableSchema.html)` を使用すると、拡張クライアントは DynamoDB 属性値をクライアント側クラスとの間でマッピングできるようになります。このチュートリアルでは、静的データクラスから派生し、ビルダーを使用してコードから生成された `TableSchema` について説明します。

## 注釈付きデータクラスを使用する
<a name="ddb-en-client-gs-tableschema-anno-bean"></a>

SDK for Java 2.x には、データクラスと一緒に使用することで、Java Bean で使用して、クラスをテーブルにマッピングするための `TableSchema` をすばやく生成できる[一連の注釈](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/mapper/annotations/package-summary.html)が含まれています。

まず、[JavaBean 仕様](https://download.oracle.com/otn-pub/jcp/7224-javabeans-1.01-fr-spec-oth-JSpec/beans.101.pdf)に準拠するデータクラスを作成します。この仕様では、クラスには引数のないパブリックコンストラクタが必要であり、クラスの各属性にはゲッターとセッターが必要です。データクラスが `DynamoDbBean` であることを示すクラスレベルの注釈を含めます。また、少なくとも、 getters と setters にはプライマリキー属性の `DynamoDbPartitionKey` 注釈を含めます。

[属性レベルの注釈](ddb-en-client-anno-index.md)はゲッターまたはセッターに適用できますが、両方に適用することはできません。

**注記**  
通常、`property` という用語は JavaBean にカプセル化された値に使用されます。ただし、このガイドでは、DynamoDB で使用されている用語との一貫性を保つため、代わりに `attribute` という用語を使用しています。

次の `Customer` クラスは、クラス定義を DynamoDB テーブルにリンクする注釈を示しています。

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

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

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

import java.time.Instant;

@DynamoDbBean
public class Customer {

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

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

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

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

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

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

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

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

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

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

注釈付きデータクラスを作成したら、次のスニペットに示すように、それを使って `TableSchema` を作成します。

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

`TableSchema` は静的で変更不可能であるように設計されています。通常、クラスロード時にインスタンス化できます。

静的 `TableSchema.fromBean()` ファクトリメソッドは Bean をイントロスペクトして、データクラス属性 (プロパティ) と DynamoDB 属性間のマッピングを生成します。

複数のデータクラスで構成されるデータモデルの操作例については、[Bean、Map、List、Set の属性を使用する](ddb-en-client-adv-features-nested.md) セクションの「`Person` クラス」を参照してください。

## ビルダーを使用する
<a name="ddb-en-client-gs-tableschema-builder"></a>

テーブルスキーマをコードで定義すれば、Bean イントロスペクションのコストを省くことができます。スキーマをコーディングする場合、クラスは JavaBean の命名基準に従う必要も、注釈を付ける必要もありません。次の例ではビルダーを使用しており、注釈を使用する `Customer` クラスの例と同じです。

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

# 拡張クライアントと `DynamoDbTable` を作成する
<a name="ddb-en-client-getting-started-dynamodbTable"></a>

## 拡張クライアントを作成する
<a name="ddb-en-client-getting-started-dynamodbTable-eclient"></a>

[DDynamoDbEnhancedClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedClient.html) クラスまたはその非同期クラスである [DynamoDBEnhancedAsyncClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedAsyncClient.html) は、DynamoDB Enhanced Client API を操作するためのエントリポイントです。

拡張クライアントでは、作業を実行するための標準の `[DynamoDbClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/DynamoDbClient.html)` が必要です。API には、`DynamoDbEnhancedClient` インスタンスを作成する 2 つの方法があります。以下のスニペットに示す 1 つ目のオプションは、構成設定から選択したデフォルト設定を使用して標準の `DynamoDbClient` を作成します。

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

基礎となる標準クライアントを設定する場合は、次のスニペットに示すように、拡張クライアントのビルダーメソッドにそれを提供することができます。

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

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

## `DynamoDbTable` インスタンスを作成する
<a name="ddb-en-client-getting-started-dynamodbTable-table"></a>

[https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbTable.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbTable.html) は、`TableSchema` によって提供されるマッピング機能を使用する DynamoDB テーブルのクライアント側表現と考えます。`DynamoDbTable` クラスには、単一の DynamoDB テーブルを操作できる CRUD オペレーションのメソッドが用意されています。

`DynamoDbTable<T>` は、カスタムクラスでもドキュメントタイプのアイテムを操作するときの `EnhancedDocument` でも、単一の型引数を取る汎用クラスです。この引数タイプは、使用するクラスと単一の DynamoDB テーブルとの関係を確立します。

次のスニペットに示すように、`DynamoDbEnhancedClient` の `table()` ファクトリメソッドを使用して、`DynamoDbTable` インスタンスを作成します。

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

`DynamoDbTable` インスタンスは不変であり、アプリケーション全体で使用できるので、シングルトンの候補です。

これで、コードに `Customer` インスタンスを使用できる DynamoDB テーブルのインメモリ表現ができました。実際の DynamoDB テーブルは、存在する場合と存在しない場合があります。`Customer` という名前のテーブルがすでに存在する場合は、そのテーブルに対して CRUD オペレーションを実行し始めることができます。テーブルが見つからない場合、次のセクションで説明するように、`DynamoDbTable` インスタンスを使用してテーブルを作成します。

# 必要に応じて、DynamoDB テーブルを作成する
<a name="ddb-en-client-gs-ddbtable"></a>

`DynamoDbTable` インスタンスを作成したら、そのインスタンスを使用して DynamoDB でテーブルを *1 回*だけ作成します。

## テーブル作成例コード
<a name="ddb-en-client-gs-ddbtable-createex"></a>

次の例では、`Customer` データクラスに基づいて DynamoDB テーブルを作成します。

この例では、クラス名と同じ `Customer` という名前で DynamoDB テーブルを作成しますが、テーブル名は別の名前でもかまいません。テーブルにどのような名前を付けるにしても、テーブルを操作するには他のアプリケーションでもこの名前を使用する必要があります。基になる DynamoDB テーブルを操作するために、別の `DynamoDbTable` オブジェクトを作成するときはいつでも、この名前を `table()` メソッドに指定してください。

`createTable` メソッドに渡される Java lambda パラメータ、`builder`、によって、[テーブルをカスタマイズ](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/CreateTableEnhancedRequest.Builder.html)できます。この例では、[プロビジョニングされたスループット](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.ReadWriteCapacityMode.html#HowItWorks.ProvisionedThroughput.Manual)が設定されています。テーブルの作成時にデフォルト設定を使用する場合は、次のスニペットに示すようにビルダーをスキップします。

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

デフォルト設定を使用すると、プロビジョニングされたスループットの値は設定されません。代わりに、テーブルの請求モードは[オンデマンド](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.ReadWriteCapacityMode.html#HowItWorks.OnDemand)に設定されます。

この例では、レスポンスで受け取ったテーブル名を出力しようとする前に、`[DynamoDbWaiter](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/waiters/DynamoDbWaiter.html)` を使用しています。テーブルの作成には少し時間がかかります。したがって、ウェーターを使用すると、テーブルを使用する前に DynamoDB サービスをポーリングしてテーブルが存在するかどうかを確認するロジックを記述する必要がなくなります。

### インポート
<a name="ddb-en-client-gs-ddbtable-imports"></a>

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

### コード
<a name="ddb-en-client-gs-ddbtable-code"></a>

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

**注記**  
DynamoDB テーブルの属性名は、テーブルがデータクラスから生成される場合、小文字で始まります。テーブルの属性名を大文字で始めたい場合は、[`@DynamoDbAttribute(NAME)` 注釈](ddb-en-client-adv-features-inex-attr.md)を使用して、必要な名前をパラメータとして指定します。

# オペレーションを実行する
<a name="ddb-en-client-gs-use"></a>

テーブルが作成されたら、`DynamoDbTable` インスタンスを使用して DynamoDB テーブルに対して操作を実行します。

次の例では、[`Customer` データクラス](ddb-en-client-gs-tableschema.md#ddb-en-client-gs-tableschema-anno-bean-cust)のインスタンスとともにシングルトン `DynamoDbTable<Customer>` をパラメータとして渡し、テーブルに新しいアイテムを追加します。

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

## `Customer` オブジェクト
<a name="perform_ops_create_customer_instatnce"></a>

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

`customer` オブジェクトを DynamoDB サービスに送信する前に、オブジェクトの `toString()` メソッドの出力をログに記録して、拡張クライアントが送信するものと比較します。

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

ワイヤレベルのログ記録には、生成されたリクエストのペイロードが表示されます。拡張クライアントはデータクラスから低レベルの表現を生成しました。Java の `Instant` タイプである `regDate` 属性は、DynamoDB の文字列として表されます。

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

# 既存のテーブルを使用する
<a name="ddb-en-client-gs-existingtable"></a>

前のセクションでは、Java データクラスから DynamoDB テーブルを作成する方法を示しました。既存のテーブルがあって、拡張クライアントの機能を使いたい場合は、そのテーブルを操作する Java データクラスを作成できます。DynamoDB テーブルを調べ、必要な注釈をデータクラスに追加する必要があります。

既存のテーブルを操作する前に、`DynamoDbEnhanced.table()` メソッドを呼び出します。これは、前の例で次のステートメントを使用して行われました。

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

`DynamoDbTable` インスタンスが返されたら、基になるテーブルですぐに作業を開始できます。`DynamoDbTable.createTable()` メソッドを呼び出してテーブルを再作成する必要はありません。

次の例では、DynamoDB テーブルから `Customer` インスタンスをすぐに取得することで示されます。

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

**重要**  
`table()` メソッドで使用されるテーブル名は、既存の DynamoDB テーブル名と一致する必要があります。

# DynamoDB Enhanced Client API の基礎について学ぶ
<a name="ddb-en-client-use"></a>

このトピックでは、DynamoDB Enhanced Client API の基本機能について説明し、[標準の DynamoDB client API](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/package-summary.html) と比較します。

DynamoDB Enhanced Client API を初めて使用する場合は、[入門チュートリアル](ddb-en-client-getting-started.md)を読んで基本的なクラスに慣れることをおすすめします。

## Java の DynamoDB アイテム
<a name="ddb-en-client-use-usecase"></a>

DynamoDB テーブルにはアイテムが保存されます。ユースケースに応じて、Java 側のアイテムは静的に構造化されたデータまたは動的に作成された構造の形をとることができます。

ユースケースで一貫性のある属性セットを持つアイテムが必要な場合は、[注釈付きクラス](ddb-en-client-gs-tableschema.md#ddb-en-client-gs-tableschema-anno-bean)を使用するか、[ビルダー](ddb-en-client-gs-tableschema.md#ddb-en-client-gs-tableschema-builder)を使用して適切な静的型 `TableSchema` を生成します。

あるいは、さまざまな構造から構成されるアイテムを保存する必要がある場合は、`DocumentTableSchema` を作成します。`DocumentTableSchema` は[拡張ドキュメント API](ddb-en-client-doc-api.md) の一部であり、必要なのは静的型プライマリキーのみで、`EnhancedDocument` インスタンスと連携してデータ要素を保持します。拡張ドキュメント API については別の[トピック](ddb-en-client-doc-api.md)で説明しています。

## データモデルクラスの属性型
<a name="ddb-en-client-use-types"></a>

DynamoDB は、Java の豊富なタイプシステムに比べて[少数の属性型](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes)をサポートしています。DynamoDB Enhanced Client API には Java クラスのメンバーと DynamoDB 属性タイプを相互に変換するメカニズムを提供します。

Java データクラスの属性型 (プロパティ) は、プリミティブではなくオブジェクト型である必要があります。たとえば、 `Long` および `int` プリミティブではなく、常に `long` および `Integer` オブジェクトデータ型を使用します。

デフォルトでは、DynamoDB Enhanced Client API は、[整数](https://docs.oracle.com/javase/8/docs/api/java/lang/Integer.html)、[文字列](https://docs.oracle.com/javase/8/docs/api/java/lang/String.html)、[BigDecimal](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/internal/converter/attribute/BigDecimalAttributeConverter.html)、[インスタント](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/internal/converter/attribute/InstantAsStringAttributeConverter.html)など、多数のタイプの属性コンバーターをサポートしています。このリストは、[AttributeConverter インターフェイスの既知の実装クラス](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/AttributeConverter.html)に表示されます。リストには、マップ、リスト、セットなど、さまざまなタイプとコレクションが含まれています。

デフォルトではサポートされていない属性型や JavaBean 規約に準拠していない属性型のデータを保存するには、変換を行うカスタム `AttributeConverter` 実装を記述できます。[例](ddb-en-client-adv-features-conversion.md#ddb-en-client-adv-features-conversion-example)については、「属性変換」セクションを参照してください。

クラスが Java Bean 仕様に準拠する属性型 (または[不変データクラス](ddb-en-client-use-immut.md)) のデータを保存するには、2 つの方法があります。
+ ソースファイルにアクセスできる場合は、クラスに `@DynamoDbBean` (または `@DynamoDbImmutable`) という注釈を付けることができます。ネストされた属性について説明しているセクションでは、注釈付きクラスの使用[例](ddb-en-client-adv-features-nested.md#ddb-en-client-adv-features-nested-map-anno)を示しています。
+ 属性の JavaBean データクラスのソースファイルにアクセスできない (またはアクセス権限のあるクラスのソースファイルに注釈を付けたくない) 場合は、ビルダーアプローチを使用できます。これにより、キーを定義せずにテーブルスキーマが作成されます。次に、このテーブルスキーマを別のテーブルスキーマ内にネストしてマッピングを実行できます。ネストされた属性セクションには、ネストされたスキーマの[使用例](ddb-en-client-adv-features-nested.md#ddb-en-client-adv-features-nested-map-builder)があります。

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

`putItem` メソッドを使用する場合、拡張クライアントは、マッピングされたデータオブジェクトの NULL 値属性を DynamoDB へのリクエストに含めません。

`updateItem` リクエストに対する SDK のデフォルト動作は、`updateItem` メソッドで送信するオブジェクトで null に設定されている属性を DynamoDB の項目から削除します。一部の属性値を更新し、他の属性値を変更しないようにするには、2 つのオプションがあります。
+ 値を変更する前に (`getItem` を使用して) 項目を取得します。このアプローチを使用すると、SDK はすべての更新された値と古い値を DynamoDB に送信します。
+ 項目を更新するリクエストを構築するときは、`[IgnoreNullsMode](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/IgnoreNullsMode.html).SCALAR_ONLY` または `IgnoreNullsMode.MAPS_ONLY` のいずれかを使用します。どちらのモードも、DynamoDB のスカラー属性を表すオブジェクト内の null 値のプロパティを無視します。このガイドの [複合型を含む項目の更新](ddb-en-client-adv-features-nested.md#ddb-en-client-adv-features-nested-updates) トピックには、`IgnoreNullsMode` 値の詳細と複合型の使用方法が記載されています。

次の例は、`updateItem()` メソッドの `ignoreNullsMode()` を示しています。

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

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

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

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

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

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


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

## DynamoDB Enhanced Client API の基本メソッド
<a name="ddb-en-client-use-basic-ops"></a>

拡張クライアントの基本的なメソッドは、その名前に由来する DynamoDB サービスオペレーションにマッピングされます。次の例は、各方法の最も単純なバリエーションを示しています。拡張リクエストオブジェクトを渡すことで、各メソッドをカスタマイズできます。拡張リクエストオブジェクトは、標準の DynamoDB クライアントで使用できるほとんどの機能を提供します。これらは、 AWS SDK for Java 2.x API リファレンスで完全に文書化されています。

この例では前に示した [`Customer` クラス](ddb-en-client-gs-tableschema.md#ddb-en-client-gs-tableschema-anno-bean-cust) を使用しています。

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

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

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

// PutItem
customerTable.putItem(customer);

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

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

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

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

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

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

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

## DynamoDB Enhanced Client を標準の DynamoDB クライアントと比較する
<a name="ddb-en-client-use-compare"></a>

DynamoDB client API ([標準](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/package-summary.html)と[拡張](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/package-summary.html)) はどちらも、DynamoDB テーブルを操作して CRUD (作成、読み取り、更新、削除) データレベルの操作を実行できます。クライアント API の違いは、その方法にあります。標準クライアントを使用すると、低レベルのデータ属性を直接操作できます。拡張クライアント API では、使い慣れた Java クラスが使用され、バックグラウンドで低レベル API にマッピングされます。

どちらのクライアント API もデータレベルの操作をサポートしていますが、標準の DynamoDB クライアントはリソースレベルの操作もサポートします。リソースレベルのオペレーションは、バックアップの作成、テーブルの一覧表示、テーブルの更新など、データベースを管理します。拡張クライアント API は、テーブルの作成、説明、削除など、特定の数のリソースレベルの操作をサポートします。

2 つのクライアント API が使用するさまざまなアプローチの違いを説明するために、以下のコード例は、標準クライアントと拡張クライアントを使用して同じ `ProductCatalog` テーブルを作成する方法を示しています。

### 比較: 標準の DynamoDB クライアントを使用してテーブルを作成
<a name="ddb-en-client-use-compare-cs1"></a>

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

### 比較: DynamoDB Enhanced Client を使用したテーブルの作成
<a name="ddb-en-client-use-compare-cs2"></a>

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

拡張クライアントは、以下の注釈付きデータクラスを使用します。DynamoDB Enhanced Client は、Java データ型を DynamoDB データ型にマッピングして、より簡潔でわかりやすいコードにします。`ProductCatalog` は、DynamoDB Enhanced Client で不変クラスを使用する例です。マッピングされたデータクラスでの不変クラスの使用については、このトピックの[後半で説明します](ddb-en-client-use-immut.md)。

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

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

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

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

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


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

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

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


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

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

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

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

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

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

次の 2 つのバッチ文字起こしのコード例は、拡張クライアントではなく標準クライアントを使用する場合の冗長性とタイプの安全性の欠如を示しています。

### 比較: 標準の DynamoDB クライアントを使ったバッチ書き込み
<a name="ddb-en-client-use-compare-cs4"></a>

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

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

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

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

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

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

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

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

### 比較: DynamoDB Enhanced Client を使ったバッチ書き込み
<a name="ddb-en-client-use-compare-cs5"></a>

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

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

# 不変データクラスでの操作
<a name="ddb-en-client-use-immut"></a>

DynamoDB Enhanced Client API のマッピング機能は、不変データクラスで動作します。不変クラスにはゲッターしかなく、SDK がクラスのインスタンスを作成するために使用するビルダークラスが必要です。不変クラスは、[カスタマークラス](ddb-en-client-gs-tableschema.md#ddb-en-client-gs-tableschema-anno-bean-cust)に示されている `@DynamoDbBean` 注釈を使用する代わりに、使用するビルダークラスを示すパラメータを受け取る `@DynamoDbImmutable` 注釈を使用します。

次のクラスは `Customer` の不変バージョンです。

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

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

import java.time.Instant;

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

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

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

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

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

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

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

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

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

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

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

データクラスに `@DynamoDbImmutable` 注釈を付けるには、次の要件を満たす必要があります。

1. `Object.class` のオーバーライドされておらず、`@DynamoDbIgnore` 注釈も付いていないすべてのメソッドは、DynamoDB テーブルの属性のゲッターでなければなりません。

1. すべてのゲッターには、ビルダークラスに対応する大文字と小文字を区別するセッターが必要です。

1. 次のコンストラクト条件のうち 1 つだけ満たす必要があります。
   + ビルダークラスにはパブリックデフォルトコンストラクタが必要です。
   + データクラスには、パラメータを取らずにビルダークラスのインスタンスを返す、`builder()` という名前のパブリック静的メソッドが必要です。このオプションは不変 `Customer` クラスに表示されます。

1.  ビルダークラスには、パラメータを取らずに不変クラスのインスタンスを返す、`build()` という名前のパブリックメソッドが必要です。

不変クラスの `TableSchema` を作成するには、次のスニペットに示すように `TableSchema` の `fromImmutableClass()` メソッドを使用します。

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

不変クラスから DynamoDB テーブルを作成できるのと同様に、次のスニペットの例に示すように、`DynamoDbTable` の `createTable()` を *1 回*呼び出すだけで不変クラスからテーブルを作成できます。

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

## Lombok などのサードパーティライブラリを使用します。
<a name="ddb-en-client-use-immut-lombok"></a>

[Project Lombok](https://projectlombok.org/) などのサードパーティライブラリは、不変オブジェクトに関連するボイラープレートコードを生成するのに役立ちます。DynamoDB Enhanced Client API は、データクラスがこのセクションで説明する規則に従っている限り、これらのライブラリで動作します。

次の例は、Lombok 注釈付きの不変 `CustomerImmutable` クラスを示しています。Lombok の `onMethod` 機能が、`@DynamoDbPartitionKey` のような属性ベースの DynamoDB 注釈を生成されたコードにコピーすることに注意してください。

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

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

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

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

# 式と条件を使用する
<a name="ddb-en-client-expressions"></a>

DynamoDB Enhanced Client API の式は、[DynamoDB 式](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.html)の Java の表現です。

DynamoDB Enhanced Client API では、次の 3 種類の式を使用します。

[Expression](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/Expression.html)  
`Expression` クラスは、条件とフィルタを定義するときに使用されます。

[https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/QueryConditional.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/QueryConditional.html)  
このタイプの式は、クエリオペレーションの[キー条件](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Query.html#Query.KeyConditionExpressions)を表します。

[https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/update/UpdateExpression.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/update/UpdateExpression.html)  
このクラスは DynamoDB [更新式](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html)の記述に役立ち、現在、拡張フレームワークでアイテムを更新する際に使用されています。

## 式の構造分析
<a name="ddb-en-client-expressions-compoonents"></a>

式は以下のような構成になっています。
+ 文字列式 (必須)。文字列には、属性名と属性値のプレースホルダー名を含む DynamoDB 論理式が含まれています。
+ 式値のマップ (通常は必須)。
+ 式名のマップ (オプション)。

ビルダーを使用して、次のような一般的な形式の `Expression` オブジェクトを生成します。

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

`Expression` は通常、式値のマップを必要とします。マップは文字列式のプレースホルダーの値を提供します。マップキーはコロン (`:`) が付いたプレースホルダー名で構成され、マップ値は [AttributeValue](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/AttributeValue.html) のインスタンスです。[AttributeValues](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/internal/AttributeValues.html) クラスには、リテラルから `AttributeValue` インスタンスを生成する便利なメソッドがあります。または、`AttributeValue.Builder` を使用して `AttributeValue` インスタンスを生成することもできます。

次のスニペットは、コメント行 2 の後に 2 つのエントリがあるマップを示しています。コメント行 1 の後に表示される `expression()` メソッドに渡される文字列には、DynamoDB が操作を実行する前に DynamoDB が解決するプレースホルダーが含まれています。*price* は許容される属性名であるため、このスニペットには式名のマップは含まれていません。

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

DynamoDB テーブル内の属性名が予約語か、数値で始まるか、またはスペースを含む場合、`Expression` には式名のマップが必要です。

たとえば、属性名が前のコード例で `price` ではなく `1price` だった場合は、次の例のように変更する必要があります。

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

式名のプレースホルダーはポンド記号 (`#`) で始まります。式名のマップのエントリでは、プレースホルダーをキー、属性名を値として使用します。マップは `expressionNames()` メソッドで式ビルダーに追加されます。DynamoDB は操作を実行する前に属性名を解決します。

文字列式で関数を使用する場合、式の値は不要です。式関数の例は `attribute_exists(<attribute_name>)` です。

次の例では、[DynamoDB 関数](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html#Expressions.OperatorsAndFunctions.Functions)を使用する `Expression` を構築します。この例の式文字列はプレースホルダーを使用していません。この式を `putItem` オペレーションに使用すると、`movie` 属性値がデータオブジェクトの `movie` 属性と等しいアイテムがデータベースにすでに存在するかどうかを確認できます。

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

DynamoDB デベロッパーガイドには、DynamoDB で使用される[低レベルの式](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.html)に関する完全な情報が記載されています。

## 条件式と条件
<a name="ddb-en-client-expressions-cond"></a>

`putItem()`、`updateItem()`、`deleteItem()` メソッドを使用する場合、トランザクションオペレーションやバッチオペレーションを使用する場合は、`[Expression](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/Expression.html)` オブジェクトを使用して、DynamoDB が操作を続行するために満たすべき条件を指定します。これらの式は条件式と呼ばれています。例については、このガイドの[トランザクション例](ddb-en-client-use-multiop-trans.md#ddb-en-client-use-multiop-trans-writeitems-opcondition)の `addDeleteItem()` メソッド (コメント行 1 の後) で使われている条件式を参照してください。

`query()` メソッドを操作する場合、条件は [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/QueryConditional.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/QueryConditional.html) として表されます。`QueryConditional` クラスには、DynamoDB から読み取るアイテムを決定する基準を記述するのに役立つ静的で便利なメソッドがいくつかあります。

`QueryConditionals` の例については、このガイドの [`Query` メソッドの例](ddb-en-client-use-multirecord.md#ddb-en-client-use-multirecord-query-example) セクションの最初のコード例を参照してください。

## フィルタ式
<a name="ddb-en-client-expressions-filter"></a>

フィルタ式は、スキャンやクエリオペレーションで、返されるアイテムをフィルターするために使用されます。

フィルタ式はデータベースからすべてのデータが読み取られた後に適用されるため、読み取りコストはフィルターがない場合と同じになります。*Amazon DynamoDB デベロッパーガイド*には、[クエリ](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Query.html#Query.FilterExpression)オペレーションと[スキャン](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Scan.html#Scan.FilterExpression)オペレーションの両方にフィルター式を使用する方法の詳細が記載されています。

次の例は、スキャンリクエストに追加されたフィルタ式を示しています。この条件では、返品されるアイテムは、価格が 8.00 から 80.00 までの価格に限定されます。

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

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

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

DynamoDB Enhanced Client の `updateItem()` メソッドは、DynamoDB 内のアイテムを更新する標準的な方法を提供します。ただし、より多くの機能が必要な場合は、[UpdateExpressions](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/update/UpdateExpression.html) によって DynamoDB [更新式の構文](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html)をタイプセーフに表現します。たとえば、`UpdateExpressions` を使用して、最初に DynamoDB からアイテムを読み取らずに値を増やしたり、リストに個々のメンバーを追加したりできます。現在、更新式は `updateItem()` メソッドのカスタム拡張で使用できます。

更新式の使用例については、このガイドの[カスタム拡張の例](ddb-en-client-extensions-custom.md)を参照してください。

更新式の詳細については、「[Amazon DynamoDB デベロッパーガイド](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html)」を参照してください。

# ページ分割された結果を処理する: スキャンとクエリ
<a name="ddb-en-client-use-multirecord"></a>

DynamoDB Enhanced Client API の`scan`、`query`、および `batch` メソッドは、1 つ以上の*ページ*を含むレスポンスを返します。ページには、1 つ以上のアイテムが含まれます。コードはページごとにレスポンスを処理することも、個々のアイテムを処理することもできます。

同期 `DynamoDbEnhancedClient` クライアントから返されるページ分割されたレスポンスは [PageIterable](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/PageIterable.html) オブジェクトを返し、非同期 `DynamoDbEnhancedAsyncClient` から返されるレスポンスは [PagePublisher](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/PagePublisher.html) オブジェクトを返します。

このセクションでは、ページ分割された結果の処理について説明し、スキャン API とクエリ API を使用する例を紹介します。

## テーブルのスキャン
<a name="ddb-en-client-use-multirecord-scan"></a>

SDK [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbAsyncTable.html#scan(java.util.function.Consumer)](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbAsyncTable.html#scan(java.util.function.Consumer)) のメソッドは、同じ名前の [DynamoDB オペレーション](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html)に対応しています。DynamoDB Enhanced Client API にも同じオプションがありますが、使い慣れたオブジェクトモデルを使用してページ分割を処理します。

まず、同期マッピングクラス [DynamoDbTable](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbTable.html) の `scan` メソッドを見て、`PageIterable` インターフェイスを調べます。

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

次の例は、[式](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/Expression.html)を使用して返されるアイテムをフィルタリングする `scan` メソッドを示しています。[ProductCatalog](ddb-en-client-use.md#ddb-en-client-use-compare-cs3) は、前に示したモデルオブジェクトです。

コメント行 2 の後に表示されるフィルタリング式は、返される`ProductCatalog`項目を 8.00～80.00 の料金値を持つ項目に制限します。

この例では、コメント行 1 の後に示されている `attributesToProject`メソッドを使用して`isbn`値を除外します。

コメント行 3 の後、`scan` メソッドによって `PageIterable` オブジェクトの `pagedResults` が返されます。`PageIterable` の `stream` メソッドは、ページの処理に使用できる [https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html) オブジェクトを返します。この例では、ページ数がカウントされ、ログに記録されます。

コメント行 4 から始まる例では、`ProductCatalog` アイテムへのアクセス方法が 2 種類示されています。コメント行 4a 以降のバージョンは、各ページをストリーミングし、各ページの項目をソートしてログに記録します。コメント行 4b 以降のバージョンは、ページの反復をスキップし、項目に直接アクセスします。

`PageIterable` インターフェースには、[https://docs.oracle.com/javase/8/docs/api/java/lang/Iterable.html](https://docs.oracle.com/javase/8/docs/api/java/lang/Iterable.html) と [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/pagination/sync/SdkIterable.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/pagination/sync/SdkIterable.html) という 2 つの親インターフェースがあるため、結果を処理する方法が複数あります。`Iterable` は `forEach`、`iterator`、`spliterator` メソッドを、`SdkIterable` は `stream` メソッドを提供します。

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

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

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

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

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

### 非同期 API を使用する
<a name="ddb-en-client-use-multirecord-scan-async"></a>

非同期 `scan` メソッドは結果を `PagePublisher` オブジェクトとして返します。`PagePublisher` インターフェースには、レスポンスページの処理に使用できる `subscribe` メソッドが 2 つあります。`subscribe` メソッドの一つは、`org.reactivestreams.Publisher` 親インターフェースからのものです。この最初のオプションを使用してページを処理するには、`subscribe` メソッドに `[Subscriber](https://www.reactive-streams.org/reactive-streams-1.0.0-javadoc/org/reactivestreams/Subscriber.html)` インスタンスを渡します。次の最初の例は、`subscribe` メソッドの使用例を示しています。

2 つ目の `subscribe` メソッドは、[SdkPublisher](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/async/SdkPublisher.html) インターフェイスからのものです。このバージョンの `subscribe` は、`Subscriber` ではなく [https://docs.oracle.com/javase/8/docs/api/java/util/function/Consumer.html](https://docs.oracle.com/javase/8/docs/api/java/util/function/Consumer.html) を受け入れます。この `subscribe` メソッドのバリエーションは、次の 2 番目の例に示されています。

次の例は、前の例で示されたのと同じフィルタ式を使用する非同期バージョンの `scan` メソッドです。

コメント行 3 の後、`DynamoDbAsyncTable.scan` は `PagePublisher` オブジェクトを返します。次の行では、コードによって `org.reactivestreams.Subscriber` インターフェース、`ProductCatalogSubscriber` のインスタンスが作成され、コメント行 4 の後に `PagePublisher` がサブスクライブされます。

`Subscriber` オブジェクトは、`ProductCatalogSubscriber` クラスの例のコメント行 8 の後に、`onNext` メソッドの各ページから `ProductCatalog` アイテムを収集します。アイテムはプライベート変数 `List` に保存され、`ProductCatalogSubscriber.getSubscribedItems()` メソッドを使用して呼び出し元のコードからアクセスされます。これはコメント行 5 の後に呼び出されます。

リストが取得されると、コードはすべての `ProductCatalog` アイテムを価格順に並べ替え、各アイテムをログに記録します。

`ProductCatalogSubscriber` クラスの [CountDownLatch](https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/CountDownLatch.html) は、コメント行 5 以降を継続する前に、すべてのアイテムがリストに追加されるまで、呼び出し元のスレッドをブロックします。

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

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

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

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

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

        @Override
        public void onError(Throwable throwable) {
        }

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

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

次のスニペット例では、コメント行 6 以降に `Consumer` を受け入れる `PagePublisher.subscribe` メソッドのバージョンを使用しています。Java ラムダパラメータはページを消費し、各アイテムをさらに処理します。この例では、各ページが処理され、各ページのアイテムがソートされてからログに記録されます。

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

`PagePublisher` の `items` メソッドはモデルインスタンスをアンラップして、コードでアイテムを直接処理できるようにします。このアプローチは次のスニペットに示されています。

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

## テーブルに対してクエリを実行する
<a name="ddb-en-client-use-multirecord-query"></a>

DynamoDB 拡張クライアントを使用してテーブルをクエリし、特定の条件に一致する複数の項目を取得できます。[https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbTable.html#query(software.amazon.awssdk.enhanced.dynamodb.model.QueryEnhancedRequest)](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbTable.html#query(software.amazon.awssdk.enhanced.dynamodb.model.QueryEnhancedRequest)) メソッドは、 データクラスで定義された `@DynamoDbPartitionKey` とオプションの `@DynamoDbSortKey` 注釈を使用して、プライマリキー値に基づいて項目を検索します。

`query()` メソッドにはパーティションキー値が必要で、オプションでソートキー条件を使用して結果をさらに絞り込むことができます。`scan` API と同様に、クエリは同期呼び出しの場合は `PageIterable` を返し、非同期呼び出しの場合は `PagePublisher` を返します。

### `Query` メソッドの例
<a name="ddb-en-client-use-multirecord-query-example"></a>

以下の `query()` メソッドコード例では `MovieActor` クラスを使用しています。データクラスは、パーティションキーとしての **`movie`** 属性と、ソートキーとしての **`actor`** 属性で構成される複合プライマリキーを定義します。

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

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

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

import java.util.Objects;

@DynamoDbBean
public class MovieActor implements Comparable<MovieActor> {

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

以下のコード例では、以下の項目に対してクエリを実行します。

#### `MovieActor` テーブル内の項目
<a name="ddb-en-client-use-movieactor-items"></a>

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

次のコードは、 `keyEqual` (コメント行 1 の後) と `sortGreaterThanOrEqualTo` (コメント行 1a の後) の 2 つの `QueryConditional` インスタンスを定義します。

#### パーティションキーで項目をクエリする
<a name="keyEqual-query-conditional-example"></a>

`keyEqual` インスタンスは、パーティションキー値が **`movie01`** の項目に一致します。

この例では、コメント行 2 の後に、**`actingschoolname`** 値がない項目を除外するフィルター式も定義しています。

`QueryEnhancedRequest` は、クエリのキー条件とフィルター式の組み合わせです。

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

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

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

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

**Example – 条件付き `keyEqual` クエリを使用した出力**  
以下は、メソッドを実行したときの出力です。出力には **movie01 ** の `movieName` 値を持つ項目が表示され、**`null`**　と等しい `actingSchoolName` の項目は表示されません。  

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

#### パーティションキーとソートキーで項目をクエリする
<a name="sort-type-query-conditional-example"></a>

`sortGreaterThanOrEqualTo` `QueryConditional` は、**actor2** 以上の値のソートキー条件を追加して、パーティションキーの一致 (**movie01**) を絞り込みます。

`sort` で始まる [`QueryConditional` メソッド](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/QueryConditional.html)では、ソートキー値に基づいた比較によってクエリを一致させ、さらに絞り込むためにパーティションキー値が必要です。メソッド名の `Sort` は、結果がソートされることを意味するのではなく、ソートキー値が比較に使用されることを意味します。

次のスニペットでは、コメント行 3 の後で、前に示したクエリリクエストを変更します。このスニペットは、「keyEqual」の条件付きクエリをコメント行 1a の後に定義された「sortGreaterThanOrEqualTo」の条件付きクエリに置き換えます。次のコードではフィルター式も削除されています。

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

**Example – 条件付き `sortGreaterThanOrEqualTo` クエリを使用した出力**  
このクエリでは次の出力が生成されます。このクエリは、**movie01** と等しい `movieName` 値を持つアイテムを返し、**actor2** 以上の `actorName` 値のアイテムのみを返します。フィルターが削除されたため、このクエリでは `actingSchoolName` 属性に値がないアイテムが返されます。  

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

# バッチオペレーションを実行する
<a name="ddb-en-client-use-multiop-batch"></a>

DynamoDB Enhanced Client API には、[`batchGetItem`()](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedClient.html#batchGetItem(java.util.function.Consumer)) と [`batchWriteItem`()](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedClient.html#batchWriteItem(java.util.function.Consumer)) の 2 つのバッチメソッドがあります。

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

[https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedClient.html#batchGetItem(java.util.function.Consumer)](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedClient.html#batchGetItem(java.util.function.Consumer)) メソッドを使用すると、1 回のリクエストで複数のテーブルから最大 100 個の項目を取得できます。次の例では、前に示した [`Customer`](ddb-en-client-gs-tableschema.md#ddb-en-client-gs-tableschema-anno-bean-cust) および [`MovieActor`](ddb-en-client-use-multirecord.md#ddb-en-client-use-movieactor-class) のデータクラスを使用しています。

1 行目と 2 行目の後の例では、3 行目のコメントの後に `batchGetItem()` メソッドにパラメータとして追加する `[ReadBatch](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/ReadBatch.html)` オブジェクトを作成します。

コメント行 1 の後のコードでは、`Customer` テーブルから読み取るバッチを構築します。コメント行 1a の後のコードは、プライマリキー値およびソートキー値を使用して読み取る項目を指定する `[GetItemEnhancedRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/GetItemEnhancedRequest.Builder.html)` ビルダーの使用を示しています。データクラスに複合キーがある場合は、パーティションキー値とソートキー値の両方を指定する必要があります。

キー値を指定して項目をリクエストするのとは対照的に、コメント行 1b の後に示されているように、データクラスを使用して項目をリクエストできます。SDK はリクエストを送信する前にバックグラウンドでキー値を抽出します。

2a の後の 2 つのステートメントに示されているように、キーベースのアプローチを使用して項目を指定する場合、DynamoDB が[強力な整合性のある読み込み](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.ReadConsistency.html)を実行するように指定することもできます。`consistentRead()` メソッドを使用する場合は、同じテーブルのリクエストされた項目すべてにそのメソッドを使用する必要があります。

DynamoDB が検出した項目を取得するには、コメント行 4 の後に表示される `[resultsForTable() ](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/BatchGetResultPage.html#resultsForTable(software.amazon.awssdk.enhanced.dynamodb.MappedTableResource))` メソッドを使用します。リクエストで読み取られた各テーブルのメソッドを呼び出します。`resultsForTable()` は見つかった項目の一覧を返し、どの `java.util.List` メソッドでも処理できます。この例では各項目を記録しています。

DynamoDB が処理しなかった項目を見つけるには、コメント行 5 の後の方法を使用します。`BatchGetResultPage` クラスには、未処理の各キーにアクセスできる `[unprocessedKeysForTable()](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/BatchGetResultPage.html#unprocessedKeysForTable(software.amazon.awssdk.enhanced.dynamodb.MappedTableResource))` メソッドがあります。[BatchGetItem API リファレンス](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_BatchGetItem.html)には、未処理のアイテムが発生する状況に関する詳細情報が記載されています。

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

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

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

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

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

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

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

サンプルコードを実行する前に、2 つのテーブルに次の項目が含まれていると仮定します。

### テーブル内の項目
<a name="ddb-en-client-use-multiop-batch-get-tableitems"></a>

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

次の出力は、コメント行 4 の後に返され、記録された項目を示しています。

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

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

`batchWriteItem()` メソッドが 1 つ以上のテーブルに 1 つ以上の項目を入力または削除します。リクエストでは、最大 25 件の個別の入力または削除操作を指定できます。次の例では、前に示した [`ProductCatalog`](ddb-en-client-use.md#ddb-en-client-use-compare-cs3) または [`MovieActor`](ddb-en-client-use-multirecord.md#ddb-en-client-use-movieactor-class) のモデルクラスを使用しています。

`WriteBatch` オブジェクトはコメント行 1 と 2 の後に作成されます。`ProductCatalog` テーブルでは、コードによって 1 つの項目が入力され、1 つの項目が削除されます。コメント 2 行目以降の `MovieActor` テーブルでは、コードによって 2 つの項目が入力され、1 つの項目が削除されます。

`batchWriteItem` メソッドはコメント行 3 の後に呼び出されます。`[builder](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/BatchWriteItemEnhancedRequest.Builder.html)` パラメータは各テーブルのバッチリクエストを提供します。

返された `[BatchWriteResult](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/BatchWriteResult.html)` オブジェクトには、未処理のリクエストを表示するための個別のメソッドが操作ごとに用意されています。コメント行 4a の後のコードは未処理の削除リクエストのキーを提供し、コメント行 4b の後のコードは未処理の入力項目を示します。

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

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

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

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

以下のヘルパーメソッドは入力操作と削除操作のモデルオブジェクトを提供します。

### ヘルパーメソッド
<a name="ddb-en-client-use-multiop-batch-write-helpers"></a>

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

サンプルコードを実行する前に、テーブルに次の項目が含まれていると仮定します。

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

サンプルコードが完了すると、テーブルには以下の項目が含まれます。

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

`MovieActor` テーブルでは、`Blue Jasmine` ムービーアイテムが、`getMovieActorBlanchettPartial()` ヘルパーメソッドを通じて取得された入力リクエストで使用されたアイテムに置き換えられていることに注意してください。データ Bean 属性値が指定されなかった場合、データベース内の値は削除されます。これが、`actingSchoolName` で `Blue Jasmine` ムービーアイテムの結果が NULL になる理由です。

**注記**  
API ドキュメントでは、条件式を使用でき、消費された容量とコレクションのメトリクスは個別の[入力](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/PutItemEnhancedRequest.html)リクエストと[削除](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/DeleteItemEnhancedRequest.html)リクエストで返すことができると記載されていますが、バッチ書き込みシナリオではそうではありません。バッチ操作のパフォーマンスを向上させるため、これらの個々のオプションは無視されます。

# トランザクションオペレーションを実行する
<a name="ddb-en-client-use-multiop-trans"></a>

DynamoDB 拡張クライアント API には、`transactGetItems()` および `transactWriteItems()` メソッドが用意されています。SDK for Java のトランザクションによって DynamoDB テーブルに不可分性、一貫性、分離性、耐久性 (ACID) が実現されるため、アプリケーション内でのデータの精度を維持することができます。

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

`[transactGetItems()](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedClient.html#transactGetItems(java.util.function.Consumer))` メソッドは、項目に対する個別のリクエストを最大 100 件受け付けます。すべてのアイテムは 1 つの不可分トランザクションで読み取られます。*Amazon DynamoDB デベロッパーガイド*には、[`transactGetItems()` メソッドが失敗する原因となる条件](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/transaction-apis.html#transaction-apis-txgetitems)や、`[transactGetItem()](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/transaction-apis.html#transaction-isolation)` の呼び出し時に使用される分離レベルに関する情報が記載されています。

次の例の 1 行目のコメントのあと、コードは `[builder](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/TransactGetItemsEnhancedRequest.Builder.html)` パラメータを使用して `transactGetItems()` メソッドを呼び出します。ビルダー `[addGetItem()](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/TransactGetItemsEnhancedRequest.Builder.html#addGetItem(software.amazon.awssdk.enhanced.dynamodb.MappedTableResource,T))` は、SDK が最終リクエストの生成に使用するキー値を含むデータオブジェクトを使用して 3 回呼び出されます。

このリクエストは、コメント行 2 の後に `[Document](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/Document.html)` オブジェクトのリストを返します。返されるドキュメントのリストには、アイテムデータの null 以外の [ドキュメント](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/Document.html) インスタンスがリクエストと同じ順序で含まれています。`[Document.getItem(MappedTableResource<T> mappedTableResource)](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/Document.html#getItem(software.amazon.awssdk.enhanced.dynamodb.MappedTableResource))` メソッドは、アイテムデータが返された場合は型指定されていない `Document` オブジェクトを型付きの Java オブジェクトに変換し、それ以外の場合は null を返します。

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

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

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

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

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

コード例が実行される前は、DynamoDB テーブルには次の項目が含まれています。

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

次の出力が記録されます。リクエストされたアイテムが見つからなかった場合、`Blue Jasmine` という名前の映画のリクエストの場合とは異なり、アイテムは返されません。

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

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

`[transactWriteItems()](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedClient.html#transactWriteItems(java.util.function.Consumer))` は、複数のテーブルにわたる 1 回のアトミックトランザクションで最大 100 件の入力、更新、削除アクションを受け付けます。*Amazon DynamoDB デベロッパーガイド*には、[基盤となる DynamoDB サービスオペレーション](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/transaction-apis.html#transaction-apis-txwriteitems)の制限と障害条件に関する詳細が記載されています。

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

次の例では、2 つのテーブルに対して 4 つのオペレーションがリクエストされています。対応するモデルクラスは、前に示した [`ProductCatalog`](ddb-en-client-use.md#ddb-en-client-use-compare-cs3) および [`MovieActor`](ddb-en-client-use-multirecord.md#ddb-en-client-use-movieactor-class) です。

入力、更新、削除の 3 つの操作では、それぞれ専用のリクエストパラメータを使用して詳細を指定します。

コメント行 1 の後のコードは、`addPutItem()` メソッドの単純なバリエーションを示しています。このメソッドは、`[MappedTableResource](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/MappedTableResource.html)` オブジェクトと入力するデータオブジェクトインスタンスを受け入れます。2 行目のコメントの後のステートメントには、`[TransactPutItemEnhancedRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/TransactPutItemEnhancedRequest.html)` インスタンスを受け付けるバリエーションが表示されます。このバリエーションでは、条件式などのオプションをリクエストに追加できます。次の[例](#ddb-en-client-use-multiop-trans-writeitems-opcondition)では、個々のオペレーションの条件式を示しています。

更新操作はコメント行 3 の後に要求されます。`[TransactUpdateItemEnhancedRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/TransactUpdateItemEnhancedRequest.Builder.html)` には、SDK がモデルオブジェクトの `null` 値を使って何をするかを設定できる `ignoreNulls()` メソッドがあります。`ignoreNulls()` メソッドが true を返す場合、SDK は `null` であるデータオブジェクト属性のテーブルの属性値を削除しません。`ignoreNulls()` メソッドが false を返す場合、SDK は DynamoDB サービスにテーブル内の項目から属性を削除するようリクエストします。`ignoreNulls` のデフォルト値は false です。

コメント 4 行目の後のステートメントは、データオブジェクトを取得する削除リクエストのバリエーションを示しています。拡張クライアントは、最後のリクエストをディスパッチする前にキー値を抽出します。

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

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

以下のヘルパーメソッドは、`add*Item` パラメータのデータオブジェクトを提供します。

#### ヘルパーメソッド
<a name="ddb-en-client-use-multiop-trans-writeitems-basic-helpers"></a>

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

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

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

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

コード例が実行される前は、DynamoDB テーブルには次の項目が含まれています。

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

コードの実行が完了すると、次の項目がテーブルに表示されます。

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

2 行目の項目は削除され、3 行目と 5 行目には追加された項目が表示されます。4 行目は 1 行目の更新を示しています。アイテム上で変更されたのは `price` の値だけです。`ignoreNulls()` が false を返した場合、4 行目は次の行のようになります。

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

### コンディションチェックの例
<a name="ddb-en-client-use-multiop-trans-writeitems-checkcond"></a>

次の例は、条件チェックの使用を示しています。コンディションチェックは、アイテムの存在を確認したり、データベース内のアイテムの特定の属性の状態をチェックしたりするために使用されます。コンディションチェックでチェックされたアイテムは、トランザクション内の別のオペレーションでは使用できません。

**注記**  
同じトランザクション内の複数のオペレーションが同じ項目をターゲットとすることはできません。たとえば、同じトランザクション内で同じ項目に対してコンディションチェックと更新を実行することはできません。

この例では、トランザクションによる項目書き込みリクエストにおける各タイプの操作を 1 つずつ示しています。2 行目のコメントの後、`addConditionCheck()` メソッドは、`conditionExpression` パラメータが `false` と評価された場合にトランザクションが失敗する条件を指定します。ヘルパーメソッドブロックに表示されているメソッドから返される条件式は、ムービーの受賞年度 `Sophie's Choice` が `1982` と等しくないかどうかをチェックします。一致した場合、式は `false` と評価され、トランザクションは失敗します。

このガイドでは、別のトピックで[式](ddb-en-client-expressions.md)について詳しく説明します。

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

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

前のコード例では以下のヘルパーメソッドが使用されています。

#### ヘルパーメソッド
<a name="ddb-en-client-use-multiop-trans-writeitems-checkcond-helpers"></a>

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

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

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

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

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

コード例が実行される前は、DynamoDB テーブルには次の項目が含まれています。

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

コードの実行が完了すると、次の項目がテーブルに表示されます。

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

トランザクションが失敗したため、テーブル内の項目は変更されません。ムービー `Sophie's Choice` の `actingYear` 値は `1982` であり、`transactWriteItem()` メソッドが呼び出される前のテーブル内の項目の 2 行目に示されているとおりです。

トランザクションのキャンセル情報を取得するには、`transactWriteItems()` メソッド呼び出しを `try` ブロックし、[https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/TransactionCanceledException.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/TransactionCanceledException.html) を `catch` します。この例の 4 行目のコメントの後、コードは各 `[CancellationReason](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/CancellationReason.html)` オブジェクトをログに記録します。この例の 3 行目のコメントに続くコードでは、トランザクションが失敗した原因となった項目の値を返すように指定していたため、ログには `Sophie's Choice` ムービー項目の未加工のデータベース値が表示されます。

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

### 単一オペレーション条件の例
<a name="ddb-en-client-use-multiop-trans-writeitems-opcondition"></a>

次の例は、トランザクションリクエスト内の 1 つのオペレーションで条件を使用する方法を示しています。コメント行 1 の後の削除操作には、操作の対象項目の値をデータベースと照合する条件が含まれています。この例では、ヘルパーメソッドでコメント行 2 の後に作成した条件式は、映画の制作年が 2013 年と等しくない場合はその項目をデータベースから削除するように指定しています。

[式](ddb-en-client-expressions.md)についてはこのガイドの後半で説明します。

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

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

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

前のコード例では以下のヘルパーメソッドが使用されています。

#### ヘルパーメソッド
<a name="ddb-en-client-use-multiop-trans-writeitems-opcondition-helpers"></a>

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

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

コード例が実行される前は、DynamoDB テーブルには次の項目が含まれています。

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

コードの実行が完了すると、次の項目がテーブルに表示されます。

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

トランザクションが失敗したため、テーブル内の項目は変更されません。ムービー `Blue Jasmine` の `actingYear` 値は、コード例が実行される前の項目リストの 2 行目に表示されている `2013` です。

次の行がコンソールに記録されます。

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

# セカンダリインデックスを使用する
<a name="ddb-en-client-use-secindex"></a>

セカンダリインデックスは、クエリやスキャンの操作に使用する代替キーを定義することで、データアクセスを向上させます。グローバルセカンダリインデックス (GSI) はベーステーブルのものとは異なる可能性があるパーティションキーおよびソートキーを持ちます。対照的に、ローカルセカンダリインデックス (LSI) はプライマリインデックスのパーティションキーを使用します。

## セカンダリインデックス注釈でデータクラスに注釈を付けます。
<a name="ddb-en-client-use-secindex-annomodel"></a>

セカンダリインデックスに含まれる属性には、`@DynamoDbSecondaryPartitionKey` または `@DynamoDbSecondarySortKey` 注釈が必要です。

次のクラスは 2 つのインデックスの注釈を表示します。*SubjectLastPostedDateIndex* という名前の GSI は、パーティションキーにこの `Subject` 属性を使用し、ソートキーには `LastPostedDateTime` という属性を使用します。*ForumLastPostedDateIndex* という名前の LSI は、`ForumName` をパーティションキーとして、`LastPostedDateTime` をソートキーとして使用します。

`Subject` 属性には 2 つの役割があることに注意してください。これはプライマリキーのソートキーであり、*subjectLastPostedDateIndex* という名前の GSI のパーティションキーでもあります。

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

`MessageThread` クラスは、*Amazon DynamoDB デベロッパーガイド*の[サンプルスレッドテーブル](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/AppendixSampleTables.html)のデータクラスとして使用するのに適しています。

#### インポート
<a name="ddb-en-client-use-secindex-classimports"></a>

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

import java.util.List;
```

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

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

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

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

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

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

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

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

    public String getLastPostedBy() {
        return LastPostedBy;
    }

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

    public Integer getViews() {
        return Views;
    }

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

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

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

    public Integer getAnswered() {
        return Answered;
    }

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

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

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

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

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

## インデックスを作成する
<a name="ddb-en-client-use-secindex-confindex"></a>

SDK for Java のバージョン 2.20.86 以降では、`createTable()` メソッドはデータクラス注釈からセカンダリインデックスを自動的に生成します。デフォルトでは、ベーステーブルのすべての属性がインデックスにコピーされ、プロビジョニングされるスループット値は 20 読み取りキャパシティーユニットと 20 書き込みキャパシティーユニットです。

ただし、2.20.86 より前のバージョンの SDK を使用する場合は、次の例のようにテーブルと一緒にインデックスを作成する必要があります。この例では、`Thread` テーブルの 2 つのインデックスを構築します。[builder](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/CreateTableEnhancedRequest.Builder.html) パラメータには、1 行目と 2 行目のコメント行の後に示されているように、両方のタイプのインデックスを設定するメソッドがあります。インデックスビルダーの `indexName()` メソッドを使用して、データクラス注釈で指定されているインデックス名を目的のインデックスタイプに関連付けます。

このコードでは、すべてのテーブル属性がコメント行 3 と 4 のコメント行の後に両方のインデックスに含まれるように構成しています。[属性プロジェクション](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/LSI.html#LSI.Projections)の詳細については、「*Amazon DynamoDB デベロッパーガイド*」を参照してください。

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

## インデックスを使用してクエリを実行する
<a name="ddb-en-client-use-secindex-query"></a>

次の例では、ローカルセカンダリインデックス *ForumLastPostedDateIndex* にクエリを実行します。

2 行目のコメントに続いて、[DynamodBindEx.query ()](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbIndex.html#query(java.util.function.Consumer)) メソッドを呼び出すときに必要な [QueryConditional](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/QueryConditional.html) オブジェクトを作成します。

コメント 3 行目の後にインデックスの名前を渡すと、クエリするインデックスへの参照が得られます。4 行目のコメントに続いて、`QueryConditional` オブジェクトに渡されるインデックスの `query()` メソッドを呼び出します。

また、5 行目のコメントの後に示される 3 つの属性値を返すようにクエリを構成します。`attributesToProject()` が呼び出されない場合、クエリはすべての属性値を返します。指定した属性名は小文字で始まることに注意してください。これらの属性名は表で使用されているものと一致し、必ずしもデータクラスの属性名と一致するわけではありません。

6 行目のコメントに続いて、結果を反復処理し、クエリによって返される各項目をログ記録し、それをリストに保存して呼び出し元に返します。

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

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

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

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

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

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

クエリが実行される前は、次の項目がデータベースに存在しています。

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

1 行目と 6 行目のログ記録ステートメントは、次のようなコンソール出力になります。

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

このクエリでは、*Forum02* の `forumName` 値と *2023.03.31* 以上の `lastPostedDateTime` 値の項目が返されました。インデックスには `message` 属性に値が含まれていますが、結果には空の文字列を含む `message` 値が表示されます。これは、メッセージ属性がコメント 5 行目以降にコードによって投影されなかったためです。

# 高度なマッピング機能を使用する
<a name="ddb-en-client-adv-features"></a>

DynamoDB 拡張クライアント API の高度なテーブルスキーマ機能について説明します。

## テーブルのスキーマタイプを理解する
<a name="ddb-en-client-adv-features-schm-overview"></a>

`[TableSchema](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/TableSchema.html)` は DynamoDB 拡張クライアント API のマッピング機能へのインターフェイスです。データオブジェクトを AttributeValues のマップにマップしたり、[AttributeValues](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/AttributeValue.html) のマップからマップしたりできます。`TableSchema` オブジェクトはマッピングするテーブルの構造を知る必要があります。この構造情報は [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/TableMetadata.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/TableMetadata.html) オブジェクトに格納されます。

拡張クライアント API には、以下のようないくつかの `TableSchema` の実装があります。

### 注釈付きのクラスから生成されたテーブルスキーマ
<a name="ddb-en-client-adv-features-schema-mapped"></a>

注釈付きクラスから `TableSchema` を構築するのは比較的コストのかかる操作であり、アプリケーションの起動時に一度実行することをお勧めします。

 [BeanTableSchema](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/mapper/BeanTableSchema.html)   
この実装は Bean クラスの属性と注釈に基づいて構築されています。この方法の例は、「[はじめに」セクション](ddb-en-client-gs-tableschema.md#ddb-en-client-gs-tableschema-anno-bean)で説明されています。  
`BeanTableSchema` が期待どおりに動作しない場合は、`software.amazon.awssdk.enhanced.dynamodb.beans` のデバッグログ記録を有効にします。

[ImmutableTableSchema](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/mapper/ImmutableTableSchema.html)  
この実装は不変データクラスから構築されています。このアプローチは [不変データクラスでの操作](ddb-en-client-use-immut.md) セクションで説明されています。

### ビルダーで生成されたテーブルスキーマ
<a name="ddb-en-client-adv-features-schema-static"></a>

以下の `TableSchema` は、ビルダーを使用してコードから構築されています。このアプローチは、注釈付きのデータクラスを使用するアプローチよりもコストが低くなります。ビルダーのアプローチでは注釈を使用せず、JavaBean の命名標準も必要ありません。

[StaticTableSchema](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/mapper/StaticTableSchema.html)  
この実装は可変データクラス用に構築されています。このガイドの「はじめに」セクションでは、[ビルダーを使用して `StaticTableSchema` を生成する](ddb-en-client-gs-tableschema.md#ddb-en-client-gs-tableschema-builder)方法を説明しました。

[StaticImmutableTableSchema](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/mapper/StaticImmutableTableSchema.html)  
`StaticTableSchema` を構築する場合と同様に、不変データクラスで使用する[ビルダー](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/mapper/StaticImmutableTableSchema.html)を使用してこのタイプ `TableSchema` の実装を生成します。

### 固定スキーマのないデータ用のテーブルスキーマ
<a name="ddb-en-client-adv-features-schema-document"></a>

[DocumentTableSchema](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/document/DocumentTableSchema.html)  
`TableSchema` の他の実装とは異なり、`DocumentTableSchema` インスタンスには属性を定義しません。通常は、プライマリキーと属性コンバータープロバイダーのみを指定します。`EnhancedDocument` インスタンスは、個々の要素または JSON 文字列から構築した属性を提供します。

# 属性を明示的に含めたり除外したりします。
<a name="ddb-en-client-adv-features-inex-attr"></a>

DynamoDB 拡張クライアント API には、データクラス属性がテーブルの属性にならないようにする注釈が用意されています。この API では、データクラスの属性名とは異なる属性名を使用することもできます。

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

DynamoDB テーブルにマッピングしてはいけない属性を無視するには、その属性に `@DynamoDbIgnore` 注釈を付けます。

```
private String internalKey;

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

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

DynamoDB テーブルで使用される属性の名前を変更するには、`@DynamoDbAttribute` 注釈を付けて別の名前を指定します。

```
private String internalKey;

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

# 属性変換を制御する
<a name="ddb-en-client-adv-features-conversion"></a>

デフォルトでは、テーブルスキーマは `[AttributeConverterProvider](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/AttributeConverterProvider.html)` インターフェースのデフォルト実装を通じて、多くの一般的な Java 型のコンバーターを提供します。全体的なデフォルト動作は、カスタム `AttributeConverterProvider` 実装で変更できます。また、1 つの属性のコンバーターを変更することもできます。

使用可能なコンバーターのリストについては、「[AttributeConverter](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/AttributeConverter.html) インターフェースの Java ドキュメント」を参照してください。

## カスタム属性コンバータープロバイダーを提供する
<a name="ddb-en-client-adv-features-conversion-prov"></a>

`@DynamoDbBean` `(converterProviders = {…})` 注釈を使用して、単一の `AttributeConverterProvider` または順序付けられた `AttributeConverterProvider` のチェーンを提供することができます。どのようなカスタム `AttributeConverterProvider` でも `AttributeConverterProvider` インターフェースを拡張する必要があります。

独自の属性コンバータープロバイダーチェーンを指定すると、デフォルトのコンバータープロバイダー、`DefaultAttributeConverterProvider` がオーバーライドされることに注意してください。`DefaultAttributeConverterProvider` の機能を使用するには、チェーンに組み込む必要があります。

Bean に空の配列 `{}` に注釈を付けすることもできます。これにより、デフォルトを含むすべての属性コンバータープロバイダーの使用が無効になります。この場合、マップされるすべての属性には独自の属性コンバーターが必要です。

次のスニペットは、単一のコンバータープロバイダーを示しています。

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

}
```

次のスニペットは、コンバータープロバイダーのチェーンの使用方法を示しています。SDK デフォルトは最後に指定されるため、優先度は最も低くなります。

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

}
```

静的テーブルスキーマビルダーにも同じように機能する `attributeConverterProviders()` メソッドがあります。これは次のスニペットに示されています。

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

## 単一の属性のマッピングをオーバーライドする
<a name="ddb-en-client-adv-features-conversion-single"></a>

単一の属性のマッピング方法をオーバーライドするには、その属性に `AttributeConverter` を指定します。この追加は、テーブルスキーマの `AttributeConverterProviders` で提供されるコンバーターよりも優先されます。これにより、その属性専用のカスタムコンバーターが追加されます。他の属性は、同じタイプの属性であっても、他の属性に明示的に指定されていない限り、そのコンバーターを使用しません。

`@DynamoDbConvertedBy` 注釈は、次のスニペットに示すようにカスタム `AttributeConverter` クラスを指定するために使用されます。

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

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

静的スキーマのビルダーには、同等の属性ビルダー `attributeConverter()` メソッドがあります。このメソッドは、次に示すように、`AttributeConverter` のインスタンスを取得します。

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

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

この例は、[https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/HttpCookie.html](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/HttpCookie.html) オブジェクトの属性コンバーターを提供する `AttributeConverterProvider` 実装を示しています。

次の `SimpleUser` クラスには、`HttpCookie` のインスタンスである `lastUsedCookie` という名前の属性が含まれています。

`@DynamoDbBean` 注釈のパラメータには、コンバーターを提供する 2 つの `AttributeConverterProvider` クラスがリストされています。

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

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

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

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

        public HttpCookie getLastUsedCookie() {
            return lastUsedCookie;
        }

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

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

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

------

次の例の `CookieConverterProvider` では、`HttpCookeConverter` のインスタンスを提供しています。

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

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

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

### 変換コード
<a name="ddb-en-client-adv-features-conversion-example-code"></a>

次の `HttpCookieConverter` クラスの `transformFrom()` メソッドでは、コードは `HttpCookie` インスタンスを受け取り、属性として保存される DynamoDB マップに変換します。

`transformTo()` メソッドは DynamoDB マップパラメータを受け取り、名前と値を必要とする `HttpCookie` コンストラクターを呼び出します。

```
    public static final class HttpCookieConverter implements AttributeConverter<HttpCookie> {

        @Override
        public AttributeValue transformFrom(HttpCookie httpCookie) {

            return AttributeValue.fromM(
            Map.of ("cookieName", AttributeValue.fromS(httpCookie.getName()),
                    "cookieValue", AttributeValue.fromS(httpCookie.getValue()))
            );
        }

        @Override
        public HttpCookie transformTo(AttributeValue attributeValue) {
            Map<String, AttributeValue> map = attributeValue.m();
            return new HttpCookie(
                    map.get("cookieName").s(),
                    map.get("cookieValue").s());
        }

        @Override
        public EnhancedType<HttpCookie> type() {
            return EnhancedType.of(HttpCookie.class);
        }

        @Override
        public AttributeValueType attributeValueType() {
            return AttributeValueType.M;
        }
    }
```

# 属性の更新動作を変更する
<a name="ddb-en-client-adv-features-upd-behavior"></a>

*更新*オペレーションを実行するときに、個々の属性の更新動作をカスタマイズできます。DynamoDB 拡張クライアント API の更新オペレーションの例には、[updateItem ()](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbTable.html#updateItem(T)) と [transactWriteItems ()](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedClient.html#transactWriteItems(java.util.function.Consumer)) があります。

たとえば、**作成した日付のタイムスタンプをレコードに保存したいとします。ただし、データベースにその属性の既存の値がない場合にのみ、その値を書き込むようにしたいとします。この場合、`[WRITE\$1IF\$1NOT\$1EXISTS](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/mapper/UpdateBehavior.html#WRITE_IF_NOT_EXISTS)` 更新動作を使用します。

次の例は、`createdOn` 属性に動作を追加する注釈を示しています。

```
@DynamoDbBean
public class Customer extends GenericRecord {
    private String id;
    private Instant createdOn;

    @DynamoDbPartitionKey
    public String getId() { return this.id; }
    public void setId(String id) { this.name = id; }

    @DynamoDbUpdateBehavior(UpdateBehavior.WRITE_IF_NOT_EXISTS)
    public Instant getCreatedOn() { return this.createdOn; }    
    public void setCreatedOn(Instant createdOn) { this.createdOn = createdOn; }
}
```

次の例のコメント行 1 の後のように、静的テーブルスキーマを構築する場合にも同じ更新動作を宣言できます。

```
static final TableSchema<Customer> CUSTOMER_TABLE_SCHEMA =
     TableSchema.builder(Customer.class)
       .newItemSupplier(Customer::new)
       .addAttribute(String.class, a -> a.name("id")
                                         .getter(Customer::getId)
                                         .setter(Customer::setId)
                                         .tags(StaticAttributeTags.primaryPartitionKey()))
       .addAttribute(Instant.class, a -> a.name("createdOn")
                                          .getter(Customer::getCreatedOn)
                                          .setter(Customer::setCreatedOn)
                                          // 1. Add an UpdateBehavior.
                                          .tags(StaticAttributeTags.updateBehavior(UpdateBehavior.WRITE_IF_NOT_EXISTS)))
       .build();
```

# 他のクラスの属性をフラット化する
<a name="ddb-en-client-adv-features-flatmap"></a>

テーブルの属性が継承または構成によって複数の異なる Java クラスに分散している場合、DynamoDB Enhanced Client API では属性を 1 つのクラスにフラット化するためのサポートを提供します。

## 継承を使用する
<a name="ddb-en-client-adv-features-flatmap-inheritance"></a>

クラスで継承を使用する場合は、以下の方法で階層をフラット化してください。

### 注釈の付いた Bean を使用する
<a name="ddb-en-client-adv-features-flatmap-inheritance-anno"></a>

注釈手法では、両方のクラスに `@DynamoDbBean` 注釈が必要で、1 つのクラスに 1 つ以上のプライマリキーの注釈を付ける必要があります。

継承関係を持つデータクラスの例を以下に示します。

------
#### [ Standard data class ]

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

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

@DynamoDbBean
public abstract class GenericRecord {
    private String id;
    private String createdDate;

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

    public String getCreatedDate() { return createdDate; }
    public void setCreatedDate(String createdDate) { this.createdDate = createdDate; }
}
```

------
#### [ Lombok ]

Lombok [`onMethod` のオプション](https://projectlombok.org/features/experimental/onX)では、`@DynamoDbPartitionKey` などの属性ベースの DynamoDB 注釈が生成コードにコピーされます。

```
@DynamoDbBean
@Data
@ToString(callSuper = true)
public class Customer extends GenericRecord {
    private String name;
}

@Data
@DynamoDbBean
public abstract class GenericRecord {
    @Getter(onMethod_=@DynamoDbPartitionKey)
    private String id;
    private String createdDate;
}
```

------

### 静的スキーマを使用する
<a name="ddb-en-client-adv-features-flatmap-inheritance-static"></a>

静的スキーマアプローチでは、ビルダーの `extend()` メソッドを使用して親クラスの属性を子クラスに集約します。これは次の例のコメント行 1 の後で示されています。

```
        StaticTableSchema<org.example.tests.model.inheritance.stat.GenericRecord> GENERIC_RECORD_SCHEMA =
                StaticTableSchema.builder(org.example.tests.model.inheritance.stat.GenericRecord.class)
                        // The partition key will be inherited by the top level mapper.
                        .addAttribute(String.class, a -> a.name("id")
                                .getter(org.example.tests.model.inheritance.stat.GenericRecord::getId)
                                .setter(org.example.tests.model.inheritance.stat.GenericRecord::setId)
                                .tags(primaryPartitionKey()))
                        .addAttribute(String.class, a -> a.name("created_date")
                                .getter(org.example.tests.model.inheritance.stat.GenericRecord::getCreatedDate)
                                .setter(org.example.tests.model.inheritance.stat.GenericRecord::setCreatedDate))
                        .build();

        StaticTableSchema<org.example.tests.model.inheritance.stat.Customer> CUSTOMER_SCHEMA =
                StaticTableSchema.builder(org.example.tests.model.inheritance.stat.Customer.class)
                        .newItemSupplier(org.example.tests.model.inheritance.stat.Customer::new)
                        .addAttribute(String.class, a -> a.name("name")
                                .getter(org.example.tests.model.inheritance.stat.Customer::getName)
                                .setter(org.example.tests.model.inheritance.stat.Customer::setName))
                        // 1. Use the extend() method to collapse the parent attributes onto the child class.
                        .extend(GENERIC_RECORD_SCHEMA)     // All the attributes of the GenericRecord schema are added to Customer.
                        .build();
```

先ほどの静的スキーマの例では、次のデータクラスを使用しています。マッピングは静的テーブルスキーマの構築時に定義されるため、データクラスには注釈は必要ありません。

#### データクラス
<a name="gunk"></a>

------
#### [ Standard data class ]

```
public class Customer extends GenericRecord {
    private String name;

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


public abstract class GenericRecord {
    private String id;
    private String createdDate;

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

    public String getCreatedDate() { return createdDate; }
    public void setCreatedDate(String createdDate) { this.createdDate = createdDate; }
```

------
#### [ Lombok ]

```
@Data
@ToString(callSuper = true)
public class Customer extends GenericRecord{
    private String name;
}

@Data
public abstract class GenericRecord {
    private String id;
    private String createdDate;
}
```

------

## 構成を使用する
<a name="ddb-en-client-adv-features-flatmap-comp"></a>

クラスで構成を使用する場合は、以下の方法で階層をフラット化してください。

### 注釈の付いた Bean を使用する
<a name="ddb-en-client-adv-features-flatmap-comp-anno"></a>

`@DynamoDbFlatten` 注釈は格納されているクラスをフラット化します。

以下のデータクラスの例では、`@DynamoDbFlatten` 注釈を使用して、含まれている `GenericRecord` クラスのすべての属性を `Customer` クラスに効果的に追加しています。

------
#### [ Standard data class ]

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

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

    @DynamoDbFlatten
    public GenericRecord getRecord() { return this.record; }
    public void setRecord(GenericRecord record) { this.record = record; }

@DynamoDbBean
public class GenericRecord {
    private String id;
    private String createdDate;

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

    public String getCreatedDate() { return this.createdDate; }
    public void setCreatedDate(String createdDate) { this.createdDate = createdDate; }
}
```

------
#### [ Lombok ]

```
@Data
@DynamoDbBean
public class Customer {
    private String name;
    @Getter(onMethod_=@DynamoDbFlatten)
    private GenericRecord record;
}

@Data
@DynamoDbBean
public class GenericRecord {
    @Getter(onMethod_=@DynamoDbPartitionKey)
    private String id;
    private String createdDate;
}
```

------

フラット化された注釈を使用すると、対象となるさまざまなクラスを必要なだけフラット化できます。以下の制約が適用されます。
+ フラット化の後は、すべての属性名が一意でなければなりません。
+ パーティションキー、ソートキー、またはテーブル名は複数あってはなりません。

### 静的スキーマを使用する
<a name="ddb-en-client-adv-features-flatmap-comp-static"></a>

静的テーブルスキーマを構築するときは、ビルダーの `flatten()` メソッドを使用します。また、含まれているクラスを識別する getter メソッド と setter メソッドも指定します。

```
        StaticTableSchema<GenericRecord> GENERIC_RECORD_SCHEMA =
                StaticTableSchema.builder(GenericRecord.class)
                        .newItemSupplier(GenericRecord::new)
                        .addAttribute(String.class, a -> a.name("id")
                                .getter(GenericRecord::getId)
                                .setter(GenericRecord::setId)
                                .tags(primaryPartitionKey()))
                        .addAttribute(String.class, a -> a.name("created_date")
                                .getter(GenericRecord::getCreatedDate)
                                .setter(GenericRecord::setCreatedDate))
                        .build();

        StaticTableSchema<Customer> CUSTOMER_SCHEMA =
                StaticTableSchema.builder(Customer.class)
                        .newItemSupplier(Customer::new)
                        .addAttribute(String.class, a -> a.name("name")
                                .getter(Customer::getName)
                                .setter(Customer::setName))
                        // Because we are flattening a component object, we supply a getter and setter so the
                        // mapper knows how to access it.
                        .flatten(GENERIC_RECORD_SCHEMA, Customer::getRecord, Customer::setRecord)
                        .build();
```

先ほどの静的スキーマの例では、次のデータクラスを使用しています。

#### データクラス
<a name="ddb-en-client-adv-features-flatmap-comp-static-supporting"></a>

------
#### [ Standard data class ]

```
public class Customer {
    private String name;
    private GenericRecord record;

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

    public GenericRecord getRecord() { return this.record; }
    public void setRecord(GenericRecord record) { this.record = record; }

public class GenericRecord {
    private String id;
    private String createdDate;

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

    public String getCreatedDate() { return this.createdDate; }
    public void setCreatedDate(String createdDate) { this.createdDate = createdDate; }
}
```

------
#### [ Lombok ]

```
@Data
public class Customer {
    private String name;
    private GenericRecord record;
}

@Data
public class GenericRecord {
    private String id;
    private String createdDate;
}
```

------

ビルダーパターンを使用して、対象となるさまざまなクラスを必要なだけフラット化できます。

## 他のコードへの影響
<a name="ddb-en-client-adv-features-flatmap-compare"></a>

`@DynamoDbFlatten` 属性 (または `flatten()` ビルダーメソッド) を使用する場合、DynamoDB の項目には、構成オブジェクトの各属性の属性が含まれます。また、構成オブジェクトの属性も含まれます。

これとは対照的に、データクラスに構成クラスの注釈を付け、`@DynamoDbFlatten` を使用しない場合、その項目は構成オブジェクトとともに単一の属性として保存されます。

たとえば、[構成例によるフラット化](#ddb-en-client-adv-features-flatmap-comp-anno)で示された `Customer` クラスを、`record` 属性をフラット化する場合とフラット化しない場合で比較します。次の表に示すように、JSON との違いを視覚化できます。


****  

| フラット化あり | フラット化なし | 
| --- | --- | 
| 3 つの属性 | 2 つの属性 | 
|  <pre>{<br />  "id": "1",<br />  "createdDate": "today",<br />  "name": "my name"<br />}</pre>  |  <pre>{<br />  "id": "1",<br />  "record": {<br />      "createdDate": "today",<br />      "name": "my name"<br />  }<br />}</pre>  | 

他のコードが DynamoDB テーブルにアクセスしていて、特定の属性を見つけることを想定している場合には、この違いが重要になります。

# Bean、Map、List、Set の属性を使用する
<a name="ddb-en-client-adv-features-nested"></a>

以下に示す `Person` クラスなどの Bean 定義は、追加の属性を持つ型を参照するプロパティ (または属性) を定義する場合があります。例えば、 `Person` クラスでは、 `mainAddress` は追加の値属性を定義する `Address` Bean を参照するプロパティです。`addresses` は Java Map を参照し、その要素は `Address` Bean を参照します。これらの複合型は、DynamoDB のコンテキストでデータ値に使用するシンプルな属性のコンテナと考えることができます。

DynamoDB は、マップ、リスト、Bean などのネストされた要素の値プロパティを*ネストされた属性*として参照します。「[Amazon DynamoDB デベロッパーガイド](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes)」では、Java Map、List、または Bean の保存形式を*ドキュメントタイプ*として参照します。Java のデータ値に使用するシンプルな属性は、DynamoDB では*スカラー型*と呼ばれます。Set は同じタイプの複数のスカラー要素を含み、*セット型*と呼ばれます。

DynamoDB 拡張クライアント API は、保存時に Bean であるプロパティを DynamoDB マップドキュメントタイプに変換することに注意してください。

## `Person` クラス
<a name="ddb-en-client-adv-features-nested-person"></a>

```
@DynamoDbBean
public class Person {
    private Integer id;
    private String firstName;
    private String lastName;
    private Integer age;
    private Address mainAddress;
    private Map<String, Address> addresses;
    private List<PhoneNumber> phoneNumbers;
    private Set<String> hobbies;

    @DynamoDbPartitionKey
    public Integer getId() {
        return id;
    }

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

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public Address getMainAddress() {
        return mainAddress;
    }

    public void setMainAddress(Address mainAddress) {
        this.mainAddress = mainAddress;
    }

    public Map<String, Address> getAddresses() {
        return addresses;
    }

    public void setAddresses(Map<String, Address> addresses) {
        this.addresses = addresses;
    }

    public List<PhoneNumber> getPhoneNumbers() {
        return phoneNumbers;
    }

    public void setPhoneNumbers(List<PhoneNumber> phoneNumbers) {
        this.phoneNumbers = phoneNumbers;
    }

    public Set<String> getHobbies() {
        return hobbies;
    }

    public void setHobbies(Set<String> hobbies) {
        this.hobbies = hobbies;
    }

    @Override
    public String toString() {
        return "Person{" +
               "addresses=" + addresses +
               ", id=" + id +
               ", firstName='" + firstName + '\'' +
               ", lastName='" + lastName + '\'' +
               ", age=" + age +
               ", mainAddress=" + mainAddress +
               ", phoneNumbers=" + phoneNumbers +
               ", hobbies=" + hobbies +
               '}';
    }
}
```

## `Address` クラス
<a name="ddb-en-client-adv-features-nested-address"></a>

```
@DynamoDbBean
public class Address {
    private String street;
    private String city;
    private String state;
    private String zipCode;

    public Address() {
    }

    public String getStreet() {
        return this.street;
    }

    public String getCity() {
        return this.city;
    }

    public String getState() {
        return this.state;
    }

    public String getZipCode() {
        return this.zipCode;
    }

    public void setStreet(String street) {
        this.street = street;
    }

    public void setCity(String city) {
        this.city = city;
    }

    public void setState(String state) {
        this.state = state;
    }

    public void setZipCode(String zipCode) {
        this.zipCode = zipCode;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Address address = (Address) o;
        return Objects.equals(street, address.street) && Objects.equals(city, address.city) && Objects.equals(state, address.state) && Objects.equals(zipCode, address.zipCode);
    }

    @Override
    public int hashCode() {
        return Objects.hash(street, city, state, zipCode);
    }

    @Override
    public String toString() {
        return "Address{" +
                "street='" + street + '\'' +
                ", city='" + city + '\'' +
                ", state='" + state + '\'' +
                ", zipCode='" + zipCode + '\'' +
                '}';
    }
}
```

## `PhoneNumber` クラス
<a name="ddb-en-client-adv-features-nested-phonenumber"></a>

```
@DynamoDbBean
public class PhoneNumber {
    String type;
    String number;

    public String getType() {
        return type;
    }

    public void setType(String type) {
        this.type = type;
    }

    public String getNumber() {
        return number;
    }

    public void setNumber(String number) {
        this.number = number;
    }

    @Override
    public String toString() {
        return "PhoneNumber{" +
                "type='" + type + '\'' +
                ", number='" + number + '\'' +
                '}';
    }
}
```

## 複合型を保存する
<a name="ddb-en-client-adv-features-nested-mapping"></a>

### 注釈付きデータクラスを使用する
<a name="ddb-en-client-adv-features-nested-map-anno"></a>

カスタムクラスのネストされた属性は、注釈を付けるだけで保存できます。前に示した `Address` クラスと `PhoneNumber` クラスには、`@DynamoDbBean` 注釈のみの注釈が付けられています。DynamoDB Enhanced Client API が次のスニペットを使用して `Person` クラスのテーブルスキーマを構築すると、API は`Address` および `PhoneNumber` クラスの使用を検出し、DynamoDB と動作する対応するマッピングを構築します。

```
TableSchema<Person> personTableSchema = TableSchema.fromBean(Person.class);
```

### ビルダーで抽象スキーマを使用する
<a name="ddb-en-client-adv-features-nested-map-builder"></a>

別の方法は、次のコードに示すように、ネストされた各 Bean クラスに静的テーブルスキーマビルダーを使用することです。

`Address` および `PhoneNumber` クラスのテーブルスキーマは、DynamoDB テーブルでは使用できないという意味で抽象的です。これは、プライマリキーの定義が不足しているためです。ただし、`Person` クラスのテーブルスキーマではネストされたスキーマとして使用されます。

`PERSON_TABLE_SCHEMA` の定義のコメント 1 行目と 2 行目の後に、抽象テーブルスキーマを使用するコードが表示されます。`EnhanceType.documentOf(...)` メソッド内で `documentOf` を使用しても、メソッドが拡張ドキュメント API の `EnhancedDocument` タイプを返すことを示すわけではありません。このコンテキストの `documentOf(...)` メソッドは、テーブルスキーマ引数を使用して DynamoDB テーブル属性との間でクラス引数をマッピングする方法を知っているオブジェクトを返します。

#### 静的スキーマコード
<a name="ddb-en-client-adv-features-nested-map-builder-code"></a>

```
    // Abstract table schema that cannot be used to work with a DynamoDB table,
    // but can be used as a nested schema.
    public static final TableSchema<Address> TABLE_SCHEMA_ADDRESS = TableSchema.builder(Address.class)
        .newItemSupplier(Address::new)
        .addAttribute(String.class, a -> a.name("street")
            .getter(Address::getStreet)
            .setter(Address::setStreet))
        .addAttribute(String.class, a -> a.name("city")
            .getter(Address::getCity)
            .setter(Address::setCity))
        .addAttribute(String.class, a -> a.name("zipcode")
            .getter(Address::getZipCode)
            .setter(Address::setZipCode))
        .addAttribute(String.class, a -> a.name("state")
            .getter(Address::getState)
            .setter(Address::setState))
        .build();

    // Abstract table schema that cannot be used to work with a DynamoDB table,
    // but can be used as a nested schema.
    public static final TableSchema<PhoneNumber> TABLE_SCHEMA_PHONENUMBER = TableSchema.builder(PhoneNumber.class)
        .newItemSupplier(PhoneNumber::new)
        .addAttribute(String.class, a -> a.name("type")
            .getter(PhoneNumber::getType)
            .setter(PhoneNumber::setType))
        .addAttribute(String.class, a -> a.name("number")
            .getter(PhoneNumber::getNumber)
            .setter(PhoneNumber::setNumber))
        .build();

    // A static table schema that can be used with a DynamoDB table.
    // The table schema contains two nested schemas that are used to perform mapping to/from DynamoDB.
    public static final TableSchema<Person> PERSON_TABLE_SCHEMA =
        TableSchema.builder(Person.class)
            .newItemSupplier(Person::new)
            .addAttribute(Integer.class, a -> a.name("id")
                .getter(Person::getId)
                .setter(Person::setId)
                .addTag(StaticAttributeTags.primaryPartitionKey()))
            .addAttribute(String.class, a -> a.name("firstName")
                .getter(Person::getFirstName)
                .setter(Person::setFirstName))
            .addAttribute(String.class, a -> a.name("lastName")
                .getter(Person::getLastName)
                .setter(Person::setLastName))
            .addAttribute(Integer.class, a -> a.name("age")
                .getter(Person::getAge)
                .setter(Person::setAge))
            .addAttribute(EnhancedType.documentOf(Address.class, TABLE_SCHEMA_ADDRESS), a -> a.name("mainAddress")
                .getter(Person::getMainAddress)
                .setter(Person::setMainAddress))
            .addAttribute(EnhancedType.listOf(String.class), a -> a.name("hobbies")
                .getter(Person::getHobbies)
                .setter(Person::setHobbies))
            .addAttribute(EnhancedType.mapOf(
                EnhancedType.of(String.class),
                // 1. Use mapping functionality of the Address table schema.
                EnhancedType.documentOf(Address.class, TABLE_SCHEMA_ADDRESS)), a -> a.name("addresses")
                .getter(Person::getAddresses)
                .setter(Person::setAddresses))
            .addAttribute(EnhancedType.listOf(
                // 2. Use mapping functionality of the PhoneNumber table schema.
                EnhancedType.documentOf(PhoneNumber.class, TABLE_SCHEMA_PHONENUMBER)), a -> a.name("phoneNumbers")
                .getter(Person::getPhoneNumbers)
                .setter(Person::setPhoneNumbers))
            .build();
```

## 複合型のプロジェクト属性
<a name="ddb-en-client-adv-features-nested-projection"></a>

`query()` および `scan()` メソッドでは、`addNestedAttributeToProject()` や `attributesToProject()` などのメソッドの呼び出しを使用して、結果で返される属性を指定できます。DynamoDB 拡張クライアント API は、リクエストが送信される前に Java メソッド呼び出しパラメータを[プロジェクション式](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.ProjectionExpressions.html)に変換します。

次の例では、`Person` テーブルに 2 つの項目を設定し、3 回のスキャンオペレーションを実行します。

1 回目のスキャンでは、結果を他のスキャンオペレーションと比較するために、テーブル内のすべての項目にアクセスします。

2 回目のスキャンでは、[https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/ScanEnhancedRequest.Builder.html#addNestedAttributeToProject(software.amazon.awssdk.enhanced.dynamodb.NestedAttributeName)](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/ScanEnhancedRequest.Builder.html#addNestedAttributeToProject(software.amazon.awssdk.enhanced.dynamodb.NestedAttributeName)) ビルダーメソッドを使用して `street` 属性値のみを返します。

3 回目のスキャンオペレーションでは、[https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/ScanEnhancedRequest.Builder.html#attributesToProject(java.lang.String...)](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/ScanEnhancedRequest.Builder.html#attributesToProject(java.lang.String...)) ビルダーメソッドを使用して第 1 レベルの属性のデータ、`hobbies` を返します。`hobbies` の属性タイプはリストです。個々のリスト項目にアクセスするには、リストに対して `get()` オペレーションを実行します。

```
        personDynamoDbTable = getDynamoDbEnhancedClient().table("Person", PERSON_TABLE_SCHEMA);
        PersonUtils.createPersonTable(personDynamoDbTable, getDynamoDbClient());
        // Use a utility class to add items to the Person table.
        List<Person> personList = PersonUtils.getItemsForCount(2);
        // This utility method performs a put against DynamoDB to save the instances in the list argument.
        PersonUtils.putCollection(getDynamoDbEnhancedClient(), personList, personDynamoDbTable);

        // The first scan logs all items in the table to compare to the results of the subsequent scans.
        final PageIterable<Person> allItems = personDynamoDbTable.scan();
        allItems.items().forEach(p ->
                // 1. Log what is in the table.
                logger.info(p.toString()));

        // Scan for nested attributes.
        PageIterable<Person> streetScanResult = personDynamoDbTable.scan(b -> b
                // Use the 'addNestedAttributeToProject()' or 'addNestedAttributesToProject()' to access data nested in maps in DynamoDB.
                .addNestedAttributeToProject(
                        NestedAttributeName.create("addresses", "work", "street")
                ));

        streetScanResult.items().forEach(p ->
                //2. Log the results of requesting nested attributes.
                logger.info(p.toString()));

        // Scan for a top-level list attribute.
        PageIterable<Person> hobbiesScanResult = personDynamoDbTable.scan(b -> b
                // Use the 'attributesToProject()' method to access first-level attributes.
                .attributesToProject("hobbies"));

        hobbiesScanResult.items().forEach((p) -> {
            // 3. Log the results of the request for the 'hobbies' attribute.
            logger.info(p.toString());
            // To access an item in a list, first get the parent attribute, 'hobbies', then access items in the list.
            String hobby = p.getHobbies().get(1);
            // 4. Log an item in the list.
            logger.info(hobby);
        });
```

```
// Logged results from comment line 1.
Person{id=2, firstName='first name 2', lastName='last name 2', age=11, addresses={work=Address{street='street 21', city='city 21', state='state 21', zipCode='33333'}, home=Address{street='street 2', city='city 2', state='state 2', zipCode='22222'}}, phoneNumbers=[PhoneNumber{type='home', number='222-222-2222'}, PhoneNumber{type='work', number='333-333-3333'}], hobbies=[hobby 2, hobby 21]}
Person{id=1, firstName='first name 1', lastName='last name 1', age=11, addresses={work=Address{street='street 11', city='city 11', state='state 11', zipCode='22222'}, home=Address{street='street 1', city='city 1', state='state 1', zipCode='11111'}}, phoneNumbers=[PhoneNumber{type='home', number='111-111-1111'}, PhoneNumber{type='work', number='222-222-2222'}], hobbies=[hobby 1, hobby 11]}

// Logged results from comment line 2.
Person{id=null, firstName='null', lastName='null', age=null, addresses={work=Address{street='street 21', city='null', state='null', zipCode='null'}}, phoneNumbers=null, hobbies=null}
Person{id=null, firstName='null', lastName='null', age=null, addresses={work=Address{street='street 11', city='null', state='null', zipCode='null'}}, phoneNumbers=null, hobbies=null}

// Logged results from comment lines 3 and 4.
Person{id=null, firstName='null', lastName='null', age=null, addresses=null, phoneNumbers=null, hobbies=[hobby 2, hobby 21]}
hobby 21
Person{id=null, firstName='null', lastName='null', age=null, addresses=null, phoneNumbers=null, hobbies=[hobby 1, hobby 11]}
hobby 11
```

**注記**  
その `attributesToProject()` メソッドが、プロジェクションする属性を追加する他のビルダーメソッドに続く場合は、`attributesToProject()` に渡された属性名のリストは他のすべての属性名を置き換えます。  
次のスニペット `ScanEnhancedRequest` のインスタンスで実行されたスキャンでは、ホビーデータのみが返されます。  

```
ScanEnhancedRequest lastOverwrites = ScanEnhancedRequest.builder()
        .addNestedAttributeToProject(
                NestedAttributeName.create("addresses", "work", "street"))
        .addAttributeToProject("firstName")
        // If the 'attributesToProject()' method follows other builder methods that add attributes for projection,
        // its list of attributes replace all previous attributes.
        .attributesToProject("hobbies")
        .build();
PageIterable<Person> hobbiesOnlyResult = personDynamoDbTable.scan(lastOverwrites);
hobbiesOnlyResult.items().forEach(p ->
        logger.info(p.toString()));

// Logged results.
Person{id=null, firstName='null', lastName='null', age=null, addresses=null, phoneNumbers=null, hobbies=[hobby 2, hobby 21]}
Person{id=null, firstName='null', lastName='null', age=null, addresses=null, phoneNumbers=null, hobbies=[hobby 1, hobby 11]}
```
以下のコード例では、最初に `attributesToProject()` メソッドを使用します。この順序付けでは、要求された他のすべての属性が保持されます。  

```
ScanEnhancedRequest attributesPreserved = ScanEnhancedRequest.builder()
        // Use 'attributesToProject()' first so that the method call does not replace all other attributes
        // that you want to project.
        .attributesToProject("firstName")
        .addNestedAttributeToProject(
                NestedAttributeName.create("addresses", "work", "street"))
        .addAttributeToProject("hobbies")
        .build();
PageIterable<Person> allAttributesResult = personDynamoDbTable.scan(attributesPreserved);
allAttributesResult.items().forEach(p ->
        logger.info(p.toString()));

// Logged results.
Person{id=null, firstName='first name 2', lastName='null', age=null, addresses={work=Address{street='street 21', city='null', state='null', zipCode='null'}}, phoneNumbers=null, hobbies=[hobby 2, hobby 21]}
Person{id=null, firstName='first name 1', lastName='null', age=null, addresses={work=Address{street='street 11', city='null', state='null', zipCode='null'}}, phoneNumbers=null, hobbies=[hobby 1, hobby 11]}
```

## 式で複合型を使用する
<a name="ddb-en-client-adv-features-nested-expressions"></a>

フィルター式や条件式などの式で複合型を使用するには、参照解除演算子を使用して複合型の構造をたどります。Object と Map の場合は `. (dot)` を使用し、List 要素の場合は `[n]` (要素のシーケンス番号を囲む角括弧) を使用します。Set の個々の要素を参照することはできませんが、 [`contains` 関数](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html#Expressions.OperatorsAndFunctions.Functions)を使用できます。

次の例は、スキャンオペレーションで使用される 2 つのフィルター式を示しています。フィルター式は、結果に含める項目の一致条件を指定します。次の例では、前に示した `Person`、`Address`、および `PhoneNumber` クラスを使用しています。

```
    public void scanUsingFilterOfNestedAttr() {
        // The following is a filter expression for an attribute that is a map of Address objects.
        // By using this filter expression, the SDK returns Person objects that have an address
        // with 'mailing' as a key and 'MS2' for a state value.
        Expression addressFilter = Expression.builder()
                .expression("addresses.#type.#field = :value")
                .putExpressionName("#type", "mailing")
                .putExpressionName("#field", "state")
                .putExpressionValue(":value", AttributeValue.builder().s("MS2").build())
                .build();

        PageIterable<Person> addressFilterResults = personDynamoDbTable.scan(rb -> rb.
                filterExpression(addressFilter));
        addressFilterResults.items().stream().forEach(p -> logger.info("Person: {}", p));

        assert addressFilterResults.items().stream().count() == 1;


        // The following is a filter expression for an attribute that is a list of phone numbers.
        // By using this filter expression, the SDK returns Person objects whose second phone number
        // in the list has a type equal to 'cell'.
        Expression phoneFilter = Expression.builder()
                .expression("phoneNumbers[1].#type = :type")
                .putExpressionName("#type", "type")
                .putExpressionValue(":type", AttributeValue.builder().s("cell").build())
                .build();

        PageIterable<Person> phoneFilterResults = personDynamoDbTable.scan(rb -> rb
                .filterExpression(phoneFilter)
                .attributesToProject("id", "firstName", "lastName", "phoneNumbers")
        );

        phoneFilterResults.items().stream().forEach(p -> logger.info("Person: {}", p));

        assert phoneFilterResults.items().stream().count() == 1;
        assert phoneFilterResults.items().stream().findFirst().get().getPhoneNumbers().get(1).getType().equals("cell");
    }
```

### テーブルに入力されるヘルパーメソッド
<a name="nested-expressions-helper-method"></a>

```
    public static void populateDatabase() {
        Person person1 = new Person();
        person1.setId(1);
        person1.setFirstName("FirstName1");
        person1.setLastName("LastName1");

        Address billingAddr1 = new Address();
        billingAddr1.setState("BS1");
        billingAddr1.setCity("BillingTown1");

        Address mailing1 = new Address();
        mailing1.setState("MS1");
        mailing1.setCity("MailingTown1");

        person1.setAddresses(Map.of("billing", billingAddr1, "mailing", mailing1));

        PhoneNumber pn1_1 = new PhoneNumber();
        pn1_1.setType("work");
        pn1_1.setNumber("111-111-1111");

        PhoneNumber pn1_2 = new PhoneNumber();
        pn1_2.setType("home");
        pn1_2.setNumber("222-222-2222");

        List<PhoneNumber> phoneNumbers1 = List.of(pn1_1, pn1_2);
        person1.setPhoneNumbers(phoneNumbers1);

        personDynamoDbTable.putItem(person1);

        Person person2 = person1;
        person2.setId(2);
        person2.setFirstName("FirstName2");
        person2.setLastName("LastName2");

        Address billingAddress2 = billingAddr1;
        billingAddress2.setCity("BillingTown2");
        billingAddress2.setState("BS2");

        Address mailing2 = mailing1;
        mailing2.setCity("MailingTown2");
        mailing2.setState("MS2");

        person2.setAddresses(Map.of("billing", billingAddress2, "mailing", mailing2));

        PhoneNumber pn2_1 = new PhoneNumber();
        pn2_1.setType("work");
        pn2_1.setNumber("333-333-3333");

        PhoneNumber pn2_2 = new PhoneNumber();
        pn2_2.setType("cell");
        pn2_2.setNumber("444-444-4444");

        List<PhoneNumber> phoneNumbers2 = List.of(pn2_1, pn2_2);
        person2.setPhoneNumbers(phoneNumbers2);

        personDynamoDbTable.putItem(person2);
    }
```

### データベース内の項目の JSON 表現
<a name="nested-attributes-expression-json-items"></a>

```
{
 "id": 1,
 "addresses": {
  "billing": {
   "city": "BillingTown1",
   "state": "BS1",
   "street": null,
   "zipCode": null
  },
  "mailing": {
   "city": "MailingTown1",
   "state": "MS1",
   "street": null,
   "zipCode": null
  }
 },
 "firstName": "FirstName1",
 "lastName": "LastName1",
 "phoneNumbers": [
  {
   "number": "111-111-1111",
   "type": "work"
  },
  {
   "number": "222-222-2222",
   "type": "home"
  }
 ]
}

{
 "id": 2,
 "addresses": {
  "billing": {
   "city": "BillingTown2",
   "state": "BS2",
   "street": null,
   "zipCode": null
  },
  "mailing": {
   "city": "MailingTown2",
   "state": "MS2",
   "street": null,
   "zipCode": null
  }
 },
 "firstName": "FirstName2",
 "lastName": "LastName2",
 "phoneNumbers": [
  {
   "number": "333-333-3333",
   "type": "work"
  },
  {
   "number": "444-444-4444",
   "type": "cell"
  }
 ]
}
```

## 複合型を含む項目の更新
<a name="ddb-en-client-adv-features-nested-updates"></a>

複合型を含む項目を更新するには、2 つの基本的なアプローチがあります。
+ アプローチ 1: まず項目を取得し (`getItem` を使用)、オブジェクトを更新してから、`DynamoDbTable#updateItem` を呼び出します。
+ アプローチ 2: 項目を取得せずに新しいインスタンスを作成し、更新するプロパティを設定し、適切な値の [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/IgnoreNullsMode.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/IgnoreNullsMode.html) を設定してインスタンスを `DynamoDbTable#updateItem` に送信します。このアプローチでは、更新する前に項目を取得する必要はありません。

このセクションに示す例では、前に示した `Person`、`Address`、および `PhoneNumber` クラスを使用します。

### 更新アプローチ 1: 取得してから更新する
<a name="ddb-en-client-adv-features-nested-updates-retreive"></a>

このアプローチを使用することで、更新時にデータが失われないようにします。DynamoDB 拡張クライアント API は、DynamoDB に保存された項目の属性を使用して、複合型の値を含めて Bean を再作成します。次に、ゲッターとセッターを使用して Bean を更新する必要があります。このアプローチの欠点は、最初に項目を取得する際に発生するコストです。

次の例は、更新する前に項目を最初に取得してもデータが失われないことを示しています。

```
    public void retrieveThenUpdateExample()  {
        // Assume that we ran this code yesterday.
        Person person = new Person();
        person.setId(1);
        person.setFirstName("FirstName");
        person.setLastName("LastName");

        Address mainAddress = new Address();
        mainAddress.setStreet("123 MyStreet");
        mainAddress.setCity("MyCity");
        mainAddress.setState("MyState");
        mainAddress.setZipCode("MyZipCode");
        person.setMainAddress(mainAddress);

        PhoneNumber homePhone = new PhoneNumber();
        homePhone.setNumber("1111111");
        homePhone.setType("HOME");
        person.setPhoneNumbers(List.of(homePhone));

        personDynamoDbTable.putItem(person);

        // Assume that we are running this code now.
        // First, retrieve the item
        Person retrievedPerson = personDynamoDbTable.getItem(Key.builder().partitionValue(1).build());

        // Make any updates.
        retrievedPerson.getMainAddress().setCity("YourCity");

        // Save the updated bean. 'updateItem' returns the bean as it appears after the update.
        Person updatedPerson = personDynamoDbTable.updateItem(retrievedPerson);

        // Verify for this example.
        Address updatedMainAddress = updatedPerson.getMainAddress();
        assert updatedMainAddress.getCity().equals("YourCity");
        assert updatedMainAddress.getState().equals("MyState"); // Unchanged.
        // The list of phone numbers remains; it was not set to null;
        assert updatedPerson.getPhoneNumbers().size() == 1;
    }
```

### 更新アプローチ 2: 最初に項目を取得せずに `IgnoreNullsMode` 列挙型を使用する
<a name="ddb-en-client-adv-features-nested-updates-nullmode"></a>

DynamoDB で項目を更新するには、更新するプロパティのみを持つ新しいオブジェクトを指定し、他の値を null のままにします。このアプローチでは、オブジェクトの null 値が SDK によってどのように処理されるかと、動作を制御する方法に注意する必要があります。

SDK で無視する null 値のプロパティを指定するには、[https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/UpdateItemEnhancedRequest.Builder.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/UpdateItemEnhancedRequest.Builder.html) を構築するときに `IgnoreNullsMode` 列挙型を指定します。列挙値のいずれかを使用する例として、次のスニペットは `IgnoreNullsMode.SCALAR_ONLY` モードを使用します。

```
// Create a new Person object to update the existing item in DynamoDB.
Person personForUpdate = new Person();
personForUpdate.setId(1);
personForUpdate.setFirstName("updatedFirstName");  // 'firstName' is a top scalar property.

Address addressForUpdate = new Address();
addressForUpdate.setCity("updatedCity");
personForUpdate.setMainAddress(addressForUpdate);

personDynamoDbTable.updateItem(r -> r
                .item(personForUpdate)
                .ignoreNullsMode(IgnoreNullsMode.SCALAR_ONLY));

/* With IgnoreNullsMode.SCALAR_ONLY provided, The SDK ignores all null properties. The SDK adds or replaces
the 'firstName' property with the provided value, "updatedFirstName". The SDK updates the 'city' value of
'mainAddress', as long as the 'mainAddress' attribute already exists in DynamoDB.

In the background, the SDK generates an update expression that it sends in the request to DynamoDB.
The following JSON object is a simplified version of what it sends. Notice that the SDK includes the paths
to 'mainAddress.city' and 'firstName' in the SET clause of the update expression. No null values in
'personForUpdate' are included.

{
  "TableName": "PersonTable",
  "Key": {
    "id": {
      "N": "1"
    }
  },
  "ReturnValues": "ALL_NEW",
  "UpdateExpression": "SET #mainAddress.#city = :mainAddress_city, #firstName = :firstName",
  "ExpressionAttributeNames": {
    "#city": "city",
    "#firstName": "firstName",
    "#mainAddress": "mainAddress"
  },
  "ExpressionAttributeValues": {
    ":firstName": {
      "S": "updatedFirstName"
    },
    ":mainAddress_city": {
      "S": "updatedCity"
    }
  }
}

Had we chosen 'IgnoreNullsMode.DEFAULT' instead of 'IgnoreNullsMode.SCALAR_ONLY', the SDK would have included
null values in the "ExpressionAttributeValues" section of the request as shown in the following snippet.

  "ExpressionAttributeValues": {
    ":mainAddress": {
      "M": {
        "zipCode": {
          "NULL": true
        },
        "city": {
          "S": "updatedCity"
        },
        "street": {
          "NULL": true
        },
        "state": {
          "NULL": true
        }
      }
    },
    ":firstName": {
      "S": "updatedFirstName"
    }
  }
*/
```

[更新式](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html)の詳細については、「Amazon DynamoDB デベロッパーガイド」を参照してください。

#### `IgnoreNullsMode` オプションの説明
<a name="ignore-nulls-mode-descriptions"></a>
+ `IgnoreNullsMode.SCALAR_ONLY` - この設定を使用して、任意のレベルでスカラー属性を更新します。SDK は、null 以外のスカラー属性のみを DynamoDB に送信する更新ステートメントを作成します。SDK は、Bean または Map の null 値のスカラー属性を無視し、DynamoDB に保存された値を保持します。

  Map または Bean のスカラー属性を更新する場合、Map は DynamoDB に既に存在している必要があります。DynamoDB のオブジェクトにまだ存在しないオブジェクトに Map または Bean を追加すると、「*更新式で指定されたドキュメントパスが更新に対して無効です*」というメッセージと `DynamoDbException` が表示されます。属性を更新する前に、 `MAPS_ONLY` モードを使用して DynamoDB に Bean または Map を追加する必要があります。
+ `IgnoreNullsMode.MAPS_ONLY` - この設定を使用して、 Bean または Map プロパティを追加または置き換えます。SDK は、オブジェクトで提供される Map または Bean を置き換えるか、追加します。オブジェクト内の null の Bean または Map は無視され、DynamoDB に存在する Map が保持されます。
+ `IgnoreNullsMode.DEFAULT` - この設定では、SDK が null 値を無視することはありません。すべてのレベルの null のスカラー属性は、null に更新されます。SDK は、オブジェクト内の null 値の Bean、Map、List、または Set プロパティを DynamoDB で null に更新します。このモードを使用する場合、またはデフォルトモードであるためモードを指定しない場合、更新用のオブジェクトで提供されている値が DynamoDB で null に設定されないように、まず項目を取得する必要があります。ただし、値を null に設定することが意図されている場合は除きます。

すべてのモードで、null 以外の List または Set を持つオブジェクトを `updateItem` に提供すると、List または Set は DynamoDB に保存されます。

#### モードを使用する理由
<a name="ddb-en-client-adv-features-nested-updates-nullmodes-why"></a>

Bean または Map のあるオブジェクトを `updateItem` メソッドに渡すと、SDK は Bean のプロパティ値 (または Map 内のエントリ値) を使用して項目を更新するか、または Bean/Map 全体で DynamoDB に保存されたものを置き換える必要があるかを認識できません。

最初に項目を取得することを示した前の例から、取得なしで `mainAddress` の `city` 属性を更新してみましょう。

```
/* The retrieval example saved the Person object with a 'mainAddress' property whose 'city' property value is "MyCity".
/* Note that we create a new Person with only the necessary information to update the city value
of the mainAddress. */
Person personForUpdate = new Person();
personForUpdate.setId(1);
// The update we want to make changes the city.
Address mainAddressForUpdate = new Address();
mainAddressForUpdate.setCity("YourCity");
personForUpdate.setMainAddress(mainAddressForUpdate);

// Lets' try the following:
Person updatedPerson = personDynamoDbTable.updateItem(personForUpdate);
/*
 Since we haven't retrieved the item, we don't know if the 'mainAddress' property
 already exists, so what update expression should the SDK generate?

A) Should it replace or add the 'mainAddress' with the provided object (setting all attributes to null other than city)
   as shown in the following simplified JSON?

      {
        "TableName": "PersonTable",
        "Key": {
          "id": {
            "N": "1"
          }
        },
        "ReturnValues": "ALL_NEW",
        "UpdateExpression": "SET #mainAddress = :mainAddress",
        "ExpressionAttributeNames": {
          "#mainAddress": "mainAddress"
        },
        "ExpressionAttributeValues": {
          ":mainAddress": {
            "M": {
              "zipCode": {
                "NULL": true
              },
              "city": {
                "S": "YourCity"
              },
              "street": {
                "NULL": true
              },
              "state": {
                "NULL": true
              }
            }
          }
        }
      }
 
B) Or should it update only the 'city' attribute of an existing 'mainAddress' as shown in the following simplified JSON?

      {
        "TableName": "PersonTable",
        "Key": {
          "id": {
            "N": "1"
          }
        },
        "ReturnValues": "ALL_NEW",
        "UpdateExpression": "SET #mainAddress.#city = :mainAddress_city",
        "ExpressionAttributeNames": {
          "#city": "city",
          "#mainAddress": "mainAddress"
        },
        "ExpressionAttributeValues": {
          ":mainAddress_city": {
            "S": "YourCity"
          }
        }
      }

However, assume that we don't know if the 'mainAddress' already exists. If it doesn't exist, the SDK would try to update 
an attribute of a non-existent map, which results in an exception.

In this particular case, we would likely select option B (SCALAR_ONLY) to retain the other values of the 'mainAddress'.
*/
```

次の 2 つの例は、`MAPS_ONLY` と `SCALAR_ONLY` の列挙値の使用を示しています。`MAPS_ONLY` は Map を追加し、`SCALAR_ONLY` は Map を更新します。

##### `IgnoreNullsMode.MAPS_ONLY` の例
<a name="scalar-only-example"></a>

```
    public void mapsOnlyModeExample() {
        // Assume that we ran this code yesterday.
        Person person = new Person();
        person.setId(1);
        person.setFirstName("FirstName");

        personDynamoDbTable.putItem(person);

        // Assume that we are running this code now.

        /* Note that we create a new Person with only the necessary information to update the city value
        of the mainAddress. */
        Person personForUpdate = new Person();
        personForUpdate.setId(1);
        // The update we want to make changes the city.
        Address mainAddressForUpdate = new Address();
        mainAddressForUpdate.setCity("YourCity");
        personForUpdate.setMainAddress(mainAddressForUpdate);


        Person updatedPerson = personDynamoDbTable.updateItem(r -> r
                .item(personForUpdate)
                .ignoreNullsMode(IgnoreNullsMode.MAPS_ONLY)); // Since the mainAddress property does not exist, use MAPS_ONLY mode.
        assert updatedPerson.getMainAddress().getCity().equals("YourCity");
        assert updatedPerson.getMainAddress().getState() == null;
    }
```

##### `IgnoreNullsMode.SCALAR_ONLY example`
<a name="maps-only-example"></a>

```
    public void scalarOnlyExample() {
        // Assume that we ran this code yesterday.
        Person person = new Person();
        person.setId(1);
        Address mainAddress = new Address();
        mainAddress.setCity("MyCity");
        mainAddress.setState("MyState");
        person.setMainAddress(mainAddress);

        personDynamoDbTable.putItem(person);

        // Assume that we are running this code now.

        /* Note that we create a new Person with only the necessary information to update the city value
        of the mainAddress. */
        Person personForUpdate = new Person();
        personForUpdate.setId(1);
        // The update we want to make changes the city.
        Address mainAddressForUpdate = new Address();
        mainAddressForUpdate.setCity("YourCity");
        personForUpdate.setMainAddress(mainAddressForUpdate);

        Person updatedPerson = personDynamoDbTable.updateItem(r -> r
                .item(personForUpdate)
                .ignoreNullsMode(IgnoreNullsMode.SCALAR_ONLY)); // SCALAR_ONLY mode ignores null properties in the in mainAddress.
        assert updatedPerson.getMainAddress().getCity().equals("YourCity");
        assert updatedPerson.getMainAddress().getState().equals("MyState"); // The state property remains the same.
    }
```

モードごとに無視される null 値を確認するには、次の表を参照してください。Bean や Map を使用する場合を除き、多くの場合は `SCALAR_ONLY` と `MAPS_ONLY` のいずれかを使用できます。


**各モードで、 `updateItem` に渡されたオブジェクト内のどの null 値プロパティが SDK によって無視されますか?**  

| プロパティの型 | SCALAR\$1ONLY モードの場合 | MAPS\$1ONLY モードの場合 | DEFAULT モードの場合 | 
| --- | --- | --- | --- | 
| 上位スカラー | はい  | あり | なし | 
| Bean または Map | はい  | あり | なし | 
| Bean または Map エントリのスカラー値 | あり 1 | なし2 | いいえ | 
| List または Set | はい  | あり | なし | 

1これは、Map が既に DynamoDB に存在することを前提としています。更新のためにオブジェクトで指定した Bean または Map のスカラー値 (null または null 以外) には、値へのパスが DynamoDB に存在する必要があります。SDK は、リクエストを送信する前に `. (dot)` 参照解除演算子を使用して、属性へのパスを作成します。

2`MAPS_ONLY` モードを使用して Bean または Map を完全に置き換えたり追加したりするため、Bean または Map 内のすべての null 値は DynamoDB に保存された Map に保持されます。

# `@DynamoDbPreserveEmptyObject` で空のオブジェクトを保存します。
<a name="ddb-en-client-adv-features-empty"></a>

空のオブジェクトを含む Bean を Amazon DynamoDB に保存し、取得時に SDK に空のオブジェクトを再作成させたい場合は、内部 Bean のゲッターに `@DynamoDbPreserveEmptyObject` の注釈を付けます。

注釈の仕組みを説明するために、コード例では次の 2 つの bean を使用しています。

## 例: Bean
<a name="ddb-en-client-adv-features-empty-ex1"></a>

次のデータクラスには 2 つの `InnerBean` フィールドがあります。getter メソッド、`getInnerBeanWithoutAnno()` には `@DynamoDbPreserveEmptyObject` という注釈が付いていません。`getInnerBeanWithAnno()` メソッドには注釈が付けられています。

```
@DynamoDbBean
public class MyBean {

    private String id;
    private String name;
    private InnerBean innerBeanWithoutAnno;
    private InnerBean innerBeanWithAnno;

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

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

    public InnerBean getInnerBeanWithoutAnno() { return innerBeanWithoutAnno; }
    public void setInnerBeanWithoutAnno(InnerBean innerBeanWithoutAnno) { this.innerBeanWithoutAnno = innerBeanWithoutAnno; }

    @DynamoDbPreserveEmptyObject
    public InnerBean getInnerBeanWithAnno() { return innerBeanWithAnno; }
    public void setInnerBeanWithAnno(InnerBean innerBeanWithAnno) { this.innerBeanWithAnno = innerBeanWithAnno; }

    @Override
    public String toString() {
        return new StringJoiner(", ", MyBean.class.getSimpleName() + "[", "]")
                .add("innerBeanWithoutAnno=" + innerBeanWithoutAnno)
                .add("innerBeanWithAnno=" + innerBeanWithAnno)
                .add("id='" + id + "'")
                .add("name='" + name + "'")
                .toString();
    }
}
```

以下の `InnerBean` クラスのインスタンスは`MyBean` フィールドであり、サンプルコードで空のオブジェクトとして初期化されます。

```
@DynamoDbBean
public class InnerBean {

    private String innerBeanField;

    public String getInnerBeanField() {
        return innerBeanField;
    }

    public void setInnerBeanField(String innerBeanField) {
        this.innerBeanField = innerBeanField;
    }

    @Override
    public String toString() {
        return "InnerBean{" +
                "innerBeanField='" + innerBeanField + '\'' +
                '}';
    }
}
```

次のコード例では、初期化された内部 Bean を含む `MyBean` オブジェクトを DynamoDB に保存し、その項目を取得します。ログ出力には、`innerBeanWithoutAnno` は初期化されておらず、`innerBeanWithAnno` が作成されたことが示されています。

```
    public MyBean preserveEmptyObjectAnnoUsingGetItemExample(DynamoDbTable<MyBean> myBeanTable) {
        // Save an item to DynamoDB.
        MyBean bean = new MyBean();
        bean.setId("1");
        bean.setInnerBeanWithoutAnno(new InnerBean());   // Instantiate the inner bean.
        bean.setInnerBeanWithAnno(new InnerBean());      // Instantiate the inner bean.
        myBeanTable.putItem(bean);

        GetItemEnhancedRequest request = GetItemEnhancedRequest.builder()
                .key(Key.builder().partitionValue("1").build())
                .build();
        MyBean myBean = myBeanTable.getItem(request);

        logger.info(myBean.toString());
        // Output 'MyBean[innerBeanWithoutAnno=null, innerBeanWithAnno=InnerBean{innerBeanField='null'}, id='1', name='null']'.

        return myBean;
    }
```

## 代わりの静的スキーマ
<a name="ddb-en-client-adv-features-empty-ex1-static"></a>

Bean の注釈の代わりに、以下の `StaticTableSchema` バージョンのテーブルスキーマを使用できます。

```
    public static TableSchema<MyBean> buildStaticSchemas() {

        StaticTableSchema<InnerBean> innerBeanStaticTableSchema =
                StaticTableSchema.builder(InnerBean.class)
                        .newItemSupplier(InnerBean::new)
                        .addAttribute(String.class, a -> a.name("innerBeanField")
                                .getter(InnerBean::getInnerBeanField)
                                .setter(InnerBean::setInnerBeanField))
                        .build();

        return StaticTableSchema.builder(MyBean.class)
                .newItemSupplier(MyBean::new)
                .addAttribute(String.class, a -> a.name("id")
                        .getter(MyBean::getId)
                        .setter(MyBean::setId)
                        .addTag(primaryPartitionKey()))
                .addAttribute(String.class, a -> a.name("name")
                        .getter(MyBean::getName)
                        .setter(MyBean::setName))
                .addAttribute(EnhancedType.documentOf(InnerBean.class,
                                innerBeanStaticTableSchema),
                        a -> a.name("innerBean1")
                                .getter(MyBean::getInnerBeanWithoutAnno)
                                .setter(MyBean::setInnerBeanWithoutAnno))
                .addAttribute(EnhancedType.documentOf(InnerBean.class,
                                innerBeanStaticTableSchema,
                                b -> b.preserveEmptyObject(true)),
                        a -> a.name("innerBean2")
                                .getter(MyBean::getInnerBeanWithAnno)
                                .setter(MyBean::setInnerBeanWithAnno))
                .build();
    }
```

# ネストされたオブジェクトの NULL 属性の保存を避ける
<a name="ddb-en-client-adv-features-ignore-null"></a>

`@DynamoDbIgnoreNulls` 注釈を適用することで、データクラスオブジェクトを DynamoDB に保存するときに、ネストされたオブジェクトの null 属性をスキップできます。これとは対照的に、NULL 値を持つ最上位の属性はデータベースに保存されません。

注釈の仕組みを説明するために、コード例では次の 2 つの Bean を使用しています。

## 例: Bean
<a name="ddb-en-client-adv-features-ignore-null-ex1"></a>

次のデータクラスには 2 つの `InnerBean` フィールドがあります。getter メソッド、`getInnerBeanWithoutAnno()` には注釈が付けられていません。`getInnerBeanWithIgnoreNullsAnno()` メソッドには `@DynamoDbIgnoreNulls` という注釈が付けられています。

```
@DynamoDbBean
public class MyBean {

    private String id;
    private String name;
    private InnerBean innerBeanWithoutAnno;
    private InnerBean innerBeanWithIgnoreNullsAnno;

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

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

    public InnerBean getInnerBeanWithoutAnno() { return innerBeanWithoutAnno; }
    public void setInnerBeanWithoutAnno(InnerBean innerBeanWithoutAnno) { this.innerBeanWithoutAnno = innerBeanWithoutAnno; }

    @DynamoDbIgnoreNulls
    public InnerBean getInnerBeanWithIgnoreNullsAnno() { return innerBeanWithIgnoreNullsAnno; }
    public void setInnerBeanWithIgnoreNullsAnno(InnerBean innerBeanWithAnno) { this.innerBeanWithIgnoreNullsAnno = innerBeanWithAnno; }

    @Override
    public String toString() {
        return new StringJoiner(", ", MyBean.class.getSimpleName() + "[", "]")
                .add("innerBeanWithoutAnno=" + innerBeanWithoutAnno)
                .add("innerBeanWithIgnoreNullsAnno=" + innerBeanWithIgnoreNullsAnno)
                .add("id='" + id + "'")
                .add("name='" + name + "'")
                .toString();
    }
}
```

次の `InnerBean` クラスのインスタンスは `MyBean` のフィールドであり、次のコード例で使用されています。

```
@DynamoDbBean
public class InnerBean {

    private String innerBeanFieldString;
    private Integer innerBeanFieldInteger;

    public String getInnerBeanFieldString() { return innerBeanFieldString; }
    public void setInnerBeanFieldString(String innerBeanFieldString) { this.innerBeanFieldString = innerBeanFieldString; }

    public Integer getInnerBeanFieldInteger() { return innerBeanFieldInteger; }
    public void setInnerBeanFieldInteger(Integer innerBeanFieldInteger) { this.innerBeanFieldInteger = innerBeanFieldInteger; }

    @Override
    public String toString() {
        return new StringJoiner(", ", InnerBean.class.getSimpleName() + "[", "]")
                .add("innerBeanFieldString='" + innerBeanFieldString + "'")
                .add("innerBeanFieldInteger=" + innerBeanFieldInteger)
                .toString();
    }
}
```

次のコード例では、`InnerBean` オブジェクトを作成し、その 2 つの属性のうち 1 つだけに値を設定します。

```
    public void ignoreNullsAnnoUsingPutItemExample(DynamoDbTable<MyBean> myBeanTable) {
        // Create an InnerBean object and give only one attribute a value.
        InnerBean innerBeanOneAttributeSet = new InnerBean();
        innerBeanOneAttributeSet.setInnerBeanFieldInteger(200);

        // Create a MyBean instance and use the same InnerBean instance both for attributes.
        MyBean bean = new MyBean();
        bean.setId("1");
        bean.setInnerBeanWithoutAnno(innerBeanOneAttributeSet);
        bean.setInnerBeanWithIgnoreNullsAnno(innerBeanOneAttributeSet);

        Map<String, AttributeValue> itemMap = myBeanTable.tableSchema().itemToMap(bean, true);
        logger.info(itemMap.toString());
        // Log the map that is sent to the database.
        // {innerBeanWithIgnoreNullsAnno=AttributeValue(M={innerBeanFieldInteger=AttributeValue(N=200)}), id=AttributeValue(S=1), innerBeanWithoutAnno=AttributeValue(M={innerBeanFieldInteger=AttributeValue(N=200), innerBeanFieldString=AttributeValue(NUL=true)})}
        
        // Save the MyBean object to the table.
        myBeanTable.putItem(bean);
    }
```

DynamoDB に送信される低レベルのデータを視覚化するために、コードは `MyBean` オブジェクトを保存する前に属性マップをログ記録します。

ログ記録された出力を見ると、`innerBeanWithIgnoreNullsAnno` が 1 つの属性を出力していることが分かります。

```
innerBeanWithIgnoreNullsAnno=AttributeValue(M={innerBeanFieldInteger=AttributeValue(N=200)})
```

`innerBeanWithoutAnno` インスタンスは 2 つの属性を出力します。1 つの属性の値は 200 で、もう 1 つは NULL 値の属性です。

```
innerBeanWithoutAnno=AttributeValue(M={innerBeanFieldInteger=AttributeValue(N=200), innerBeanFieldString=AttributeValue(NUL=true)})
```

## 属性マップの JSON 表現
<a name="ddb-en-client-adv-features-ignore-null-ex2"></a>

次の JSON 表現を使用すると、DynamoDB に保存されたデータを簡単に確認できます。

```
{
  "id": {
    "S": "1"
  },
  "innerBeanWithIgnoreNullsAnno": {
    "M": {
      "innerBeanFieldInteger": {
        "N": "200"
      }
    }
  },
  "innerBeanWithoutAnno": {
    "M": {
      "innerBeanFieldInteger": {
        "N": "200"
      },
      "innerBeanFieldString": {
        "NULL": true
      }
    }
  }
}
```

## 代わりの静的スキーマ
<a name="ddb-en-client-adv-features-empty-ex1-static"></a>

次の `StaticTableSchema` バージョンのテーブルスキーマをデータクラス注釈の代わりに使用できます。

```
public static TableSchema<MyBean> buildStaticSchemas() {

    StaticTableSchema<InnerBean> innerBeanStaticTableSchema =
        StaticTableSchema.builder(InnerBean.class)
            .newItemSupplier(InnerBean::new)
            .addAttribute(String.class, a -> a.name("innerBeanFieldString")
                .getter(InnerBean::getInnerBeanFieldString)
                .setter(InnerBean::setInnerBeanFieldString))
            .addAttribute(Integer.class, a -> a.name("innerBeanFieldInteger")
                .getter(InnerBean::getInnerBeanFieldInteger)
                .setter(InnerBean::setInnerBeanFieldInteger))
            .build();

    return StaticTableSchema.builder(MyBean.class)
        .newItemSupplier(MyBean::new)
        .addAttribute(String.class, a -> a.name("id")
            .getter(MyBean::getId)
            .setter(MyBean::setId)
            .addTag(primaryPartitionKey()))
        .addAttribute(String.class, a -> a.name("name")
            .getter(MyBean::getName)
            .setter(MyBean::setName))
        .addAttribute(EnhancedType.documentOf(InnerBean.class,
                innerBeanStaticTableSchema),
            a -> a.name("innerBeanWithoutAnno")
                .getter(MyBean::getInnerBeanWithoutAnno)
                .setter(MyBean::setInnerBeanWithoutAnno))
        .addAttribute(EnhancedType.documentOf(InnerBean.class,
                innerBeanStaticTableSchema,
                b -> b.ignoreNulls(true)),
            a -> a.name("innerBeanWithIgnoreNullsAnno")
                .getter(MyBean::getInnerBeanWithIgnoreNullsAnno)
                .setter(MyBean::setInnerBeanWithIgnoreNullsAnno))
        .build();
}
```

# DynamoDB 用の拡張ドキュメント API を使用して JSON ドキュメントを操作する
<a name="ddb-en-client-doc-api"></a>

の[拡張ドキュメント API](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/document/package-summary.html) AWS SDK for Java 2.x は、固定スキーマを持たないドキュメント指向のデータで動作するように設計されています。ただし、カスタムクラスを使用して個々の属性をマップすることもできます。

 拡張ドキュメント API は、v1.x の[ドキュメント API](https://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/services/dynamodbv2/document/DynamoDB.html) AWS SDK for Java の後継です。

**Contents**
+ [拡張ドキュメント API の使用を開始する](ddb-en-client-doc-api-steps.md)
  + [`DocumentTableSchema` および `DynamoDbTable` を作成する](ddb-en-client-doc-api-steps.md#ddb-en-client-doc-api-steps-createschema)
+ [拡張ドキュメントを構築する](ddb-en-client-doc-api-steps-create-ed.md)
  + [JSON 文字列から構築する](ddb-en-client-doc-api-steps-create-ed.md#ddb-en-client-doc-api-steps-create-ed-fromJson)
  + [個々の要素から構築する](ddb-en-client-doc-api-steps-create-ed.md#ddb-en-client-doc-api-steps-create-ed-fromparts)
+ [CRUD オペレーションを実行する](ddb-en-client-doc-api-steps-use.md)
+ [拡張ドキュメント属性にはカスタムオブジェクトとしてアクセスできます。](ddb-en-client-doc-api-convert.md)
+ [DynamoDB を使用せずに `EnhancedDocument` を使用する](ddb-en-client-doc-api-standalone.md)

# 拡張ドキュメント API の使用を開始する
<a name="ddb-en-client-doc-api-steps"></a>

拡張ドキュメント API には、DynamoDB 拡張クライアント API に必要なものと同じ[依存関係](ddb-en-client-getting-started.md#ddb-en-client-gs-dep)が必要です。また、このトピックの冒頭で示したように、[`DynamoDbEnhancedClient` インスタンス](ddb-en-client-getting-started-dynamodbTable.md#ddb-en-client-getting-started-dynamodbTable-eclient)も必要です。

Enhanced Document API は のバージョン 2.20.3 でリリースされたため AWS SDK for Java 2.x、そのバージョン以降が必要です。

## `DocumentTableSchema` および `DynamoDbTable` を作成する
<a name="ddb-en-client-doc-api-steps-createschema"></a>

拡張ドキュメント API を使用して DynamoDB テーブルに対してコマンドを呼び出すには、テーブルをクライアント側の [DynamoDbTable<EnhancedDocument>](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbTable.html) リソースオブジェクトに関連付けます。

拡張クライアントの `table()` メソッドは `DynamoDbTable<EnhancedDocument>` インスタンスを作成し、DynamoDB テーブル名と `DocumentTableSchema` のパラメータを必要とします。

[DocumentTableSchema](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/document/DocumentTableSchema.html) のビルダーには、プライマリインデックスキーと 1 つ以上の属性コンバータープロバイダーが必要です。この `AttributeConverterProvider.defaultProvider()` メソッドは[デフォルトタイプ](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/internal/converter/attribute/package-summary.html)のコンバーターを提供します。カスタム属性コンバータープロバイダーを提供する場合でも、指定する必要があります。オプションのセカンダリインデックスキーをビルダーに追加できます。

次のコードスニペットは、スキーマレス `person` オブジェクトを格納する DynamoDB `EnhancedDocument` テーブルのクライアント側表現を生成するコードを示しています。

```
DynamoDbTable<EnhancedDocument> documentDynamoDbTable = 
                enhancedClient.table("person",
                        TableSchema.documentSchemaBuilder()
                            // Specify the primary key attributes.
                            .addIndexPartitionKey(TableMetadata.primaryIndexName(),"id", AttributeValueType.S)
                            .addIndexSortKey(TableMetadata.primaryIndexName(), "lastName", AttributeValueType.S)
                            // Specify attribute converter providers. Minimally add the default one.
                            .attributeConverterProviders(AttributeConverterProvider.defaultProvider())
                            .build());
                                                         
// Call documentTable.createTable() if "person" does not exist in DynamoDB.
// createTable() should be called only one time.
```

このセクション全体で使用される `person` オブジェクトの JSON 表現を以下に示します。

### JSON `person` オブジェクト
<a name="ddb-en-client-doc-api-steps-createschema-obj"></a>

```
{
  "id": 1,
  "firstName": "Richard",
  "lastName": "Roe",
  "age": 25,
  "addresses":
    {
      "home": {
        "zipCode": "00000",
        "city": "Any Town",
        "state": "FL",
        "street": "123 Any Street"
      },
      "work": {
        "zipCode": "00001",
        "city": "Anywhere",
        "state": "FL",
        "street": "100 Main Street"
      }
    },
  "hobbies": [
    "Hobby 1",
    "Hobby 2"
  ],
  "phoneNumbers": [
    {
      "type": "Home",
      "number": "555-0100"
    },
    {
      "type": "Work",
      "number": "555-0119"
    }
  ]
}
```

# 拡張ドキュメントを構築する
<a name="ddb-en-client-doc-api-steps-create-ed"></a>

`[EnhancedDocument](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/document/EnhancedDocument.html)` は、ネストされた属性を持ち、複雑な構造を持つドキュメントタイプのオブジェクトを表します。`EnhancedDocument` には、`DocumentTableSchema` に指定されたプライマリキー属性と一致する最上位の属性が必要です。残りの内容は任意で、最上位の属性だけでなく、深くネストされた属性で構成することもできます。

`EnhancedDocument` インスタンスを作成するには、複数の方法で要素を追加できるビルダーを使用します。

## JSON 文字列から構築する
<a name="ddb-en-client-doc-api-steps-create-ed-fromJson"></a>

JSON 文字列を使用して、1 つのメソッド呼び出しに `EnhancedDocument` を構築できます。次のスニペットは、`jsonPerson()` ヘルパーメソッドから返された JSON 文字列から `EnhancedDocument` を作成します。この `jsonPerson()` メソッドは、前に示した [person オブジェクト](ddb-en-client-doc-api-steps.md#ddb-en-client-doc-api-steps-createschema-obj)の JSON 文字列バージョンを返します。

```
EnhancedDocument document = 
        EnhancedDocument.builder()
                        .json( jsonPerson() )
                        .build());
```

## 個々の要素から構築する
<a name="ddb-en-client-doc-api-steps-create-ed-fromparts"></a>

また、ビルダーのタイプセーフメソッドを使用して個々のコンポーネントから `EnhancedDocument` インスタンスを構築することもできます。

次の例では、前の例の JSON 文字列から作成された拡張ドキュメントと同様の `person` 拡張ドキュメントを作成します。

```
        /* Define the shape of an address map whose JSON representation looks like the following.
           Use 'addressMapEnhancedType' in the following EnhancedDocument.builder() to simplify the code.
           "home": {
             "zipCode": "00000",
             "city": "Any Town",
             "state": "FL",
             "street": "123 Any Street"
           }*/
        EnhancedType<Map<String, String>> addressMapEnhancedType =
                EnhancedType.mapOf(EnhancedType.of(String.class), EnhancedType.of(String.class));


        //  Use the builder's typesafe methods to add elements to the enhanced document.
        EnhancedDocument personDocument = EnhancedDocument.builder()
                .putNumber("id", 50)
                .putString("firstName", "Shirley")
                .putString("lastName", "Rodriguez")
                .putNumber("age", 53)
                .putNull("nullAttribute")
                .putJson("phoneNumbers", phoneNumbersJSONString())
                /* Add the map of addresses whose JSON representation looks like the following.
                        {
                          "home": {
                            "zipCode": "00000",
                            "city": "Any Town",
                            "state": "FL",
                            "street": "123 Any Street"
                          }
                        } */
                .putMap("addresses", getAddresses(), EnhancedType.of(String.class), addressMapEnhancedType)
                .putList("hobbies", List.of("Theater", "Golf"), EnhancedType.of(String.class))
                .build();
```

### ヘルパーメソッド
<a name="ddb-en-client-doc-api-steps-use-fromparts-helpers"></a>

```
    private static String phoneNumbersJSONString() {
        return "  [" +
                "    {" +
                "      \"type\": \"Home\"," +
                "      \"number\": \"555-0140\"" +
                "    }," +
                "    {" +
                "      \"type\": \"Work\"," +
                "      \"number\": \"555-0155\"" +
                "    }" +
                "  ]";
    }

    private static Map<String, Map<String, String>> getAddresses() {
        return Map.of(
                "home", Map.of(
                        "zipCode", "00002",
                        "city", "Any Town",
                        "state", "ME",
                        "street", "123 Any Street"));

    }
```

# CRUD オペレーションを実行する
<a name="ddb-en-client-doc-api-steps-use"></a>

`EnhancedDocument` インスタンスを定義したら、DynamoDB テーブルに保存できます。次のコードスニペットでは、個々の要素から作成された [personDocument](ddb-en-client-doc-api-steps-create-ed.md#ddb-en-client-doc-api-steps-create-ed-fromparts) を使用しています。

```
documentDynamoDbTable.putItem(personDocument);
```

DynamoDB から拡張ドキュメントインスタンスを読み取った後、`personDocument` から保存されたデータにアクセスする次のコードスニペットに示すように、ゲッターを使用して個々の属性値を抽出することができます。または、サンプルコードの最後の部分に示されているように、コンテンツ全体を JSON 文字列に抽出することもできます。

```
        // Read the item.
        EnhancedDocument personDocFromDb = documentDynamoDbTable.getItem(Key.builder().partitionValue(50).build());

        // Access top-level attributes.
        logger.info("Name: {} {}", personDocFromDb.getString("firstName"), personDocFromDb.getString("lastName"));
        // Name: Shirley Rodriguez

        // Typesafe access of a deeply nested attribute. The addressMapEnhancedType shown previously defines the shape of an addresses map.
        Map<String, Map<String, String>> addresses = personDocFromDb.getMap("addresses", EnhancedType.of(String.class), addressMapEnhancedType);
        addresses.keySet().forEach(k -> logger.info(addresses.get(k).toString()));
        // {zipCode=00002, city=Any Town, street=123 Any Street, state=ME}

        // Alternatively, work with AttributeValue types checking along the way for deeply nested attributes.
        Map<String, AttributeValue> addressesMap = personDocFromDb.getMapOfUnknownType("addresses");
        addressesMap.keySet().forEach((String k) -> {
            logger.info("Looking at data for [{}] address", k);
            // Looking at data for [home] address
            AttributeValue value = addressesMap.get(k);
            AttributeValue cityValue = value.m().get("city");
            if (cityValue != null) {
                logger.info(cityValue.s());
                // Any Town
            }
        });

        List<AttributeValue> phoneNumbers = personDocFromDb.getListOfUnknownType("phoneNumbers");
        phoneNumbers.forEach((AttributeValue av) -> {
            if (av.hasM()) {
                AttributeValue type = av.m().get("type");
                if (type.s() != null) {
                    logger.info("Type of phone: {}", type.s());
                    // Type of phone: Home
                    // Type of phone: Work
                }
            }
        });

        String jsonPerson = personDocFromDb.toJson();
        logger.info(jsonPerson);
        // {"firstName":"Shirley","lastName":"Rodriguez","addresses":{"home":{"zipCode":"00002","city":"Any Town","street":"123 Any Street","state":"ME"}},"hobbies":["Theater","Golf"],
        //     "id":50,"nullAttribute":null,"age":53,"phoneNumbers":[{"number":"555-0140","type":"Home"},{"number":"555-0155","type":"Work"}]}
```

`EnhancedDocument` インスタンスは、マッピングされたデータクラスの代わりに、`[DynamoDbTable](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbTable.html)` または [DynamoDbEnhancedClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedClient.html) の任意のメソッドで使用できます。

# 拡張ドキュメント属性にはカスタムオブジェクトとしてアクセスできます。
<a name="ddb-en-client-doc-api-convert"></a>

拡張ドキュメント API では、スキーマレス構造の属性を読み書きするための API を提供するだけでなく、カスタムクラスのインスタンスとの間で属性を変換できます。

拡張ドキュメント API は、DynamoDB 拡張クライアント API の一部として[コントロール属性変換](ddb-en-client-adv-features-conversion.md)セクションに表示された `AttributeConverterProvider` と `AttributeConverter` を使用します。

次の例では、`CustomAttributeConverterProvider` をネストされた `AddressConverter` クラスとともに使用して `Address` オブジェクトを変換します。

この例は、クラスからのデータと、必要に応じて構築された構造からのデータを組み合わせることができることを示しています。この例はまた、カスタムクラスはネストされた構造のどのレベルでも使用できることを示しています。この例の `Address` オブジェクトはマップで使用される値です。

```
    public static void attributeToAddressClassMappingExample(DynamoDbEnhancedClient enhancedClient, DynamoDbClient standardClient) {
        String tableName = "customer";

        // Define the DynamoDbTable for an enhanced document.
        // The schema builder provides methods for attribute converter providers and keys.
        DynamoDbTable<EnhancedDocument> documentDynamoDbTable = enhancedClient.table(tableName,
                DocumentTableSchema.builder()
                        // Add the CustomAttributeConverterProvider along with the default when you build the table schema.
                        .attributeConverterProviders(
                                List.of(
                                        new CustomAttributeConverterProvider(),
                                        AttributeConverterProvider.defaultProvider()))
                        .addIndexPartitionKey(TableMetadata.primaryIndexName(), "id", AttributeValueType.N)
                        .addIndexSortKey(TableMetadata.primaryIndexName(), "lastName", AttributeValueType.S)
                        .build());
        // Create the DynamoDB table if needed.
        documentDynamoDbTable.createTable();
        waitForTableCreation(tableName, standardClient);


        // The getAddressesForCustomMappingExample() helper method that provides 'addresses' shows the use of a custom Address class
        // rather than using a Map<String, Map<String, String> to hold the address data.
        Map<String, Address> addresses = getAddressesForCustomMappingExample();

        // Build an EnhancedDocument instance to save an item with a mix of structures defined as needed and static classes.
        EnhancedDocument personDocument = EnhancedDocument.builder()
                .putNumber("id", 50)
                .putString("firstName", "Shirley")
                .putString("lastName", "Rodriguez")
                .putNumber("age", 53)
                .putNull("nullAttribute")
                .putJson("phoneNumbers", phoneNumbersJSONString())
                // Note the use of 'EnhancedType.of(Address.class)' instead of the more generic
                // 'EnhancedType.mapOf(EnhancedType.of(String.class), EnhancedType.of(String.class))' that was used in a previous example.
                .putMap("addresses", addresses, EnhancedType.of(String.class), EnhancedType.of(Address.class))
                .putList("hobbies", List.of("Hobby 1", "Hobby 2"), EnhancedType.of(String.class))
                .build();
        // Save the item to DynamoDB.
        documentDynamoDbTable.putItem(personDocument);

        // Retrieve the item just saved.
        EnhancedDocument srPerson = documentDynamoDbTable.getItem(Key.builder().partitionValue(50).sortValue("Rodriguez").build());

        // Access the addresses attribute.
        Map<String, Address> srAddresses = srPerson.get("addresses",
                EnhancedType.mapOf(EnhancedType.of(String.class), EnhancedType.of(Address.class)));

        srAddresses.keySet().forEach(k -> logger.info(addresses.get(k).toString()));

        documentDynamoDbTable.deleteTable();

// The content logged to the console shows that the saved maps were converted to Address instances.
Address{street='123 Main Street', city='Any Town', state='NC', zipCode='00000'}
Address{street='100 Any Street', city='Any Town', state='NC', zipCode='00000'}
```

## `CustomAttributeConverterProvider` コード
<a name="ddb-en-client-doc-api-convert-provider"></a>

```
public class CustomAttributeConverterProvider implements AttributeConverterProvider {

    private final Map<EnhancedType<?>, AttributeConverter<?>> converterCache = ImmutableMap.of(
            // 1. Add AddressConverter to the internal cache.
            EnhancedType.of(Address.class), new AddressConverter());

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

    // 2. The enhanced client queries the provider for attribute converters if it
    //    encounters a type that it does not know how to convert.
    @SuppressWarnings("unchecked")
    @Override
    public <T> AttributeConverter<T> converterFor(EnhancedType<T> enhancedType) {
        return (AttributeConverter<T>) converterCache.get(enhancedType);
    }

    // 3. Custom attribute converter
    private class AddressConverter implements AttributeConverter<Address> {
        // 4. Transform an Address object into a DynamoDB map.
        @Override
        public AttributeValue transformFrom(Address address) {

            Map<String, AttributeValue> attributeValueMap = Map.of(
                    "street", AttributeValue.fromS(address.getStreet()),
                    "city", AttributeValue.fromS(address.getCity()),
                    "state", AttributeValue.fromS(address.getState()),
                    "zipCode", AttributeValue.fromS(address.getZipCode()));

            return AttributeValue.fromM(attributeValueMap);
        }

        // 5. Transform the DynamoDB map attribute to an Address oject.
        @Override
        public Address transformTo(AttributeValue attributeValue) {
            Map<String, AttributeValue> m = attributeValue.m();
            Address address = new Address();
            address.setStreet(m.get("street").s());
            address.setCity(m.get("city").s());
            address.setState(m.get("state").s());
            address.setZipCode(m.get("zipCode").s());

            return address;
        }

        @Override
        public EnhancedType<Address> type() {
            return EnhancedType.of(Address.class);
        }

        @Override
        public AttributeValueType attributeValueType() {
            return AttributeValueType.M;
        }
    }
}
```

## `Address` クラス
<a name="ddb-en-client-doc-api-convert-address"></a>

```
public class Address {
                  private String street;
                  private String city;
                  private String state;
                  private String zipCode;

                  public Address() {
                  }

                  public String getStreet() {
                  return this.street;
                  }

                  public String getCity() {
                  return this.city;
                  }

                  public String getState() {
                  return this.state;
                  }

                  public String getZipCode() {
                  return this.zipCode;
                  }

                  public void setStreet(String street) {
                  this.street = street;
                  }

                  public void setCity(String city) {
                  this.city = city;
                  }

                  public void setState(String state) {
                  this.state = state;
                  }

                  public void setZipCode(String zipCode) {
                  this.zipCode = zipCode;
                  }
                  }
```

## アドレスを提供するヘルパーメソッド
<a name="ddb-en-client-doc-api-convert-helper"></a>

次のヘルパーメソッドは、値にジェネリック `Map<String, String>` インスタンスではなく、値にカスタム `Address` インスタンスを使用するマップを提供します。

```
    private static Map<String, Address> getAddressesForCustomMappingExample() {
        Address homeAddress = new Address();
        homeAddress.setStreet("100 Any Street");
        homeAddress.setCity("Any Town");
        homeAddress.setState("NC");
        homeAddress.setZipCode("00000");

        Address workAddress = new Address();
        workAddress.setStreet("123 Main Street");
        workAddress.setCity("Any Town");
        workAddress.setState("NC");
        workAddress.setZipCode("00000");

        return Map.of("home", homeAddress,
                "work", workAddress);
    }
```

# DynamoDB を使用せずに `EnhancedDocument` を使用する
<a name="ddb-en-client-doc-api-standalone"></a>

通常、`EnhancedDocument` のインスタンスはドキュメントタイプの DynamoDB アイテムの読み取りと書き込みに使用しますが、DynamoDB とは独立して使用することもできます。

JSON 文字列やカスタムオブジェクトを、次の例のように `AttributeValues` の低レベルのマップに変換できるようにするために `EnhancedDocuments` を使用できます。

```
    public static void conversionWithoutDynamoDbExample() {
        Address address = new Address();
        address.setCity("my city");
        address.setState("my state");
        address.setStreet("my street");
        address.setZipCode("00000");

        // Build an EnhancedDocument instance for its conversion functionality alone.
        EnhancedDocument addressEnhancedDoc = EnhancedDocument.builder()
                // Important: You must specify attribute converter providers when you build an EnhancedDocument instance not used with a DynamoDB table.
                .attributeConverterProviders(new CustomAttributeConverterProvider(), DefaultAttributeConverterProvider.create())
                .put("addressDoc", address, Address.class)
                .build();

        // Convert address to a low-level item representation.
        final Map<String, AttributeValue> addressAsAttributeMap = addressEnhancedDoc.getMapOfUnknownType("addressDoc");
        logger.info("addressAsAttributeMap: {}", addressAsAttributeMap.toString());

        // Convert address to a JSON string.
        String addressAsJsonString = addressEnhancedDoc.getJson("addressDoc");
        logger.info("addressAsJsonString: {}", addressAsJsonString);
        // Convert addressEnhancedDoc back to an Address instance.
        Address addressConverted =  addressEnhancedDoc.get("addressDoc", Address.class);
        logger.info("addressConverted: {}", addressConverted.toString());
    }

   /* Console output:
          addressAsAttributeMap: {zipCode=AttributeValue(S=00000), state=AttributeValue(S=my state), street=AttributeValue(S=my street), city=AttributeValue(S=my city)}
          addressAsJsonString: {"zipCode":"00000","state":"my state","street":"my street","city":"my city"}
          addressConverted: Address{street='my street', city='my city', state='my state', zipCode='00000'}
   */
```

**注記**  
DynamoDB テーブルから独立した拡張ドキュメントを使用する場合は、必ずビルダーに属性コンバータープロバイダーを明示的に設定してください。  
対照的に、拡張ドキュメントを DynamoDB テーブルで使用すると、ドキュメントテーブルスキーマがコンバータープロバイダーに提供されます。

# 拡張機能を使用して DynamoDB 拡張クライアントオペレーションをカスタマイズする
<a name="ddb-en-client-extensions"></a>

DynamoDB 拡張クライアント API は、マッピング操作以外の機能を提供するプラグイン拡張機能をサポートしています。拡張機能は、読み取りおよび書き込みオペレーション中に 2 つのフックメソッドを使用してデータを変更します。
+ `beforeWrite()` - 発生する前に書き込みオペレーションを変更する
+ `afterRead()` - 読み取りオペレーションの実行後にその結果を変更する

一部のオペレーション (アイテムの更新など) は書き込みと読み取りの両方を実行するため、両方のフックメソッドが呼び出されます。

## 拡張機能のロード方法
<a name="ddb-en-client-extensions-loading"></a>

拡張機能は、拡張クライアントビルダーで指定された順序でロードされます。1 つのエクステンションが以前のエクステンションによって変換された値に作用する可能性があるため、ロード順序は重要な場合があります。

デフォルトでは、拡張クライアントは 2 つの拡張機能をロードします。
+ `[VersionedRecordExtension](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/extensions/VersionedRecordExtension.html)` - 楽観的ロックを提供する
+ `[AtomicCounterExtension](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/extensions/AtomicCounterExtension.html)` - カウンター属性を自動的に増分する

拡張クライアントビルダーでデフォルトの動作を上書きして、任意の拡張機能を読み込むことができます。デフォルトの拡張機能を使いたくない場合は、none を指定することもできます。

**重要**  
独自の拡張機能をロードしても、拡張クライアントはデフォルトの拡張機能をロードしません。いずれかのデフォルトの拡張機能と同じ動作をさせたい場合は、拡張機能のリストに明示的に追加する必要があります。

次の例は、 `VersionedRecordExtension` の後に `verifyChecksumExtension` という名前のカスタム拡張機能をロードする方法を示しています。この例では、`AtomicCounterExtension` はロードされていません。

```
DynamoDbEnhancedClientExtension versionedRecordExtension = VersionedRecordExtension.builder().build();

DynamoDbEnhancedClient enhancedClient = 
    DynamoDbEnhancedClient.builder()
                          .dynamoDbClient(dynamoDbClient)
                          .extensions(versionedRecordExtension, verifyChecksumExtension)
                          .build();
```

## 利用可能な拡張機能の詳細と設定
<a name="ddb-en-client-extensions-details"></a>

以下のセクションでは、 SDK で使用可能な各拡張機能に関する詳細情報を記載します。

### `VersionedRecordExtension` を使用して楽観的ロックを実装する
<a name="ddb-en-client-extensions-VRE"></a>

`VersionedRecordExtension` 拡張機能は、アイテムがデータベースに書き込まれるとアイテムのバージョン番号を増加して追跡することで、楽観的ロックを提供します。実際に保存されているアイテムのバージョン番号がアプリケーションが最後に読み取った値と一致しない場合、書き込みが失敗する原因となる条件が書き込みごとに追加されます。

#### 設定
<a name="ddb-en-client-extensions-VRE-conf"></a>

アイテムのバージョン番号を追跡するために使用する属性を指定するには、テーブルスキーマの数値属性にタグを付けます。

次のスニペットでは、`version` 属性がアイテムのバージョン番号を保持するように指定しています。

```
    @DynamoDbVersionAttribute
    public Integer getVersion() {...};
    public void setVersion(Integer version) {...};
```

これと同等の静的テーブルスキーマのアプローチは、次のスニペットで示されます。

```
    .addAttribute(Integer.class, a -> a.name("version")
                                       .getter(Customer::getVersion)
                                       .setter(Customer::setVersion)
                                        // Apply the 'version' tag to the attribute.
                                       .tags(VersionedRecordExtension.AttributeTags.versionAttribute())
```

#### 仕組み
<a name="ddb-en-client-extensions-VRE-how-it-works"></a>

`VersionedRecordExtension` を使用した楽観的ロックは、次の `DynamoDbEnhancedClient` および `DynamoDbTable` メソッドに対して以下のような影響があります。

**`putItem`**  
新しいアイテムには、初期バージョン値 0 が割り当てられます。これは `@DynamoDbVersionAttribute(startAt = X)` で設定できます。

**`updateItem`**  
項目を取得し、その項目の 1 つ以上のプロパティを更新して変更を保存する場合には、クライアント側とサーバー側のバージョン番号が一致する場合のみ、オペレーションが成功します。  
成功すると、バージョン番号は自動的に 1 ずつ増加します。これは `@DynamoDbVersionAttribute(incrementBy = X)` で設定できます。

**`deleteItem`**  
`DynamoDbVersionAttribute` 注釈は効力を発揮しません。アイテムを削除するときは、条件式を手動で追加する必要があります。  
次の例では、条件式を追加して、削除されたアイテムが読み取られたアイテムであることを確認します。次の例では、`recordVersion` は `@DynamoDbVersionAttribute` で注釈を付けた Bean の属性です。  

```
// 1. Read the item and get its current version.
Customer item = customerTable.getItem(Key.builder().partitionValue("someId").build());
// `recordVersion` is the bean's attribute that is annotated with `@DynamoDbVersionAttribute`.
AttributeValue currentVersion = item.getRecordVersion();

// 2. Create conditional delete with the `currentVersion` value.
DeleteItemEnhancedRequest deleteItemRequest =
    DeleteItemEnhancedRequest.builder()
       .key(KEY)
       .conditionExpression(Expression.builder()
           .expression("recordVersion = :current_version_value")
           .putExpressionValue(":current_version_value", currentVersion)
           .build()).build();

customerTable.deleteItem(deleteItemRequest);
```

**`transactWriteItems`**  
+ `addPutItem`: このメソッドの動作は `putItem` と同じです。
+ `addUpdateItem`: このメソッドの動作は `updateItem` と同じです。
+ `addDeleteItem`: このメソッドの動作は `deleteItem` と同じです。

**`batchWriteItem`**  
+ `addPutItem`: このメソッドの動作は `putItem` と同じです。
+ `addDeleteItem`: このメソッドの動作は `deleteItem` と同じです。

**注記**  
DynamoDB グローバルテーブルでは[最終書き込み者優先](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/V2globaltables_HowItWorks.html#V2globaltables_HowItWorks.consistency-modes)を使用して、同時更新間の調整を行い、DynamoDB は最終書き込み者を判断するために最善を尽くします。グローバルテーブルを使用する場合、この「最終書き込み者優先」ポリシーは、すべてのレプリカが最終的に DynamoDB によって決定された最後の書き込みに基づいて収束するため、ロック戦略が期待どおりに機能しない可能性があることを意味します。

#### 無効にする方法
<a name="ddb-en-client-extensions-VRE-how-to-disable"></a>

楽観的ロックを無効にするには、`@DynamoDbVersionAttribute` 注釈を使用しないようにします。

### `AtomicCounterExtension` を使用してカウンターを実装する
<a name="ddb-en-client-extensions-ACE"></a>

`AtomicCounterExtension` 拡張機能は、レコードがデータベースに書き込まれるたびに、タグ付きの数値属性を増やします。開始値と増分値を指定できます。値を指定しない場合、開始値は 0 に設定され、属性の値は 1 ずつ増加します。

#### 設定
<a name="ddb-en-client-extensions-ACE-conf"></a>

どの属性がカウンターかを指定するには、テーブルスキーマのタイプ `Long` の属性にタグを付けます。

次のスニペットは、`counter` 属性のデフォルトの開始値と増分値の使用方法を示しています。

```
    @DynamoDbAtomicCounter
    public Long getCounter() {...};
    public void setCounter(Long counter) {...};
```

次のスニペットは、静的テーブルスキーマのアプローチを示しています。アトミックカウンタ拡張機能は、開始値を 10 に設定し、レコードが書き込まれるたびに値を 5 ずつ増やします。

```
    .addAttribute(Integer.class, a -> a.name("counter")
                                       .getter(Customer::getCounter)
                                       .setter(Customer::setCounter)
                                        // Apply the 'atomicCounter' tag to the attribute with start and increment values.
                                       .tags(StaticAttributeTags.atomicCounter(10L, 5L))
```

### `AutoGeneratedTimestampRecordExtension` でタイムスタンプを追加する
<a name="ddb-en-client-extensions-AGTE"></a>

`AutoGeneratedTimestampRecordExtension` 拡張機能は、アイテムがデータベースに正常に書き込まれるたびに、そのタイプ `[Instant](https://docs.oracle.com/javase/8/docs/api/java/time/Instant.html)` のタグ付き属性を現在のタイムスタンプで自動的に更新します。このエクステンションはデフォルトではロードされません。

#### 設定
<a name="ddb-en-client-extensions-AGTE-conf"></a>

現在のタイムスタンプで更新する属性を指定するには、テーブルスキーマの `Instant` 属性にタグを付けます。

次のスニペットでは、`lastUpdate` 属性が拡張機能の動作の対象となります。属性は `Instant` タイプでなければならないという要件に注意してください。

```
    @DynamoDbAutoGeneratedTimestampAttribute
    public Instant getLastUpdate() {...}
    public void setLastUpdate(Instant lastUpdate) {...}
```

これと同等の静的テーブルスキーマのアプローチは、次のスニペットで示されます。

```
     .addAttribute(Instant.class, a -> a.name("lastUpdate")
                                        .getter(Customer::getLastUpdate)
                                        .setter(Customer::setLastUpdate)
                                        // Applying the 'autoGeneratedTimestamp' tag to the attribute.
                                        .tags(AutoGeneratedTimestampRecordExtension.AttributeTags.autoGeneratedTimestampAttribute())
```

### AutoGeneratedUuidExtension を使用して UUID を生成する
<a name="ddb-en-client-extensions-AGUE"></a>

`AutoGeneratedUuidExtension` 拡張機能は、新しいレコードがデータベースに書き込まれるときに、属性の一意の UUID (ユニバーサル一意識別子) を生成します。Java JDK [UUID.randomUUID()](https://docs.oracle.com/javase/8/docs/api/java/util/UUID.html#randomUUID--) メソッドを使用し、`java.lang.String` 型の属性に適用されます。このエクステンションはデフォルトではロードされません。

#### 設定
<a name="ddb-en-client-extensions-AGUE-conf"></a>

次のスニペットでは、`uniqueId` 属性が拡張機能の動作の対象となります。

```
    @AutoGeneratedUuidExtension
    public String getUniqueId() {...}
    public void setUniqueId(String uniqueId) {...}
```

これと同等の静的テーブルスキーマのアプローチは、次のスニペットで示されます。

```
     .addAttribute(String.class, a -> a.name("uniqueId")
                                        .getter(Customer::getUniqueId)
                                        .setter(Customer::setUniqueId)
                                        // Applying the 'autoGeneratedUuid' tag to the attribute.
                                        .tags(AutoGeneratedUuidExtension.AttributeTags.autoGeneratedUuidAttribute())
```

拡張機能で `putItem` メソッドに対してのみ UUID に入力されるようにし、`updateItem` メソッドに対しては入力されないようにするには、次のスニペットに示すように、[更新動作](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/mapper/UpdateBehavior.html)の注釈を追加します。

```
    @AutoGeneratedUuidExtension
    @DynamoDbUpdateBehavior(UpdateBehavior.WRITE_IF_NOT_EXISTS)
    public String getUniqueId() {...}
    public void setUniqueId(String uniqueId) {...}
```

静的テーブルスキーマアプローチを使用する場合は、次の同等のコードを使用します。

```
     .addAttribute(String.class, a -> a.name("uniqueId")
                                        .getter(Customer::getUniqueId)
                                        .setter(Customer::setUniqueId)
                                        // Applying the 'autoGeneratedUuid' tag to the attribute.
                                        .tags(AutoGeneratedUuidExtension.AttributeTags.autoGeneratedUuidAttribute(),
                                              StaticAttributeTags.updateBehavior(UpdateBehavior.WRITE_IF_NOT_EXISTS))
```

# カスタム拡張機能の例
<a name="ddb-en-client-extensions-custom"></a>

`DynamoDbEnhancedClientExtension` インターフェイスを実装することで、カスタム拡張機能を作成できます。次のカスタム拡張機能クラスは、データベース内の項目にまだ属性がない場合に、更新式を使用して `registrationDate` 属性を設定する `beforeWrite()` メソッドを示しています。

```
public final class CustomExtension implements DynamoDbEnhancedClientExtension {

    // 1. In a custom extension, use an UpdateExpression to define what action to take before
    //    an item is updated.
    @Override
    public WriteModification beforeWrite(DynamoDbExtensionContext.BeforeWrite context) {
        if ( context.operationContext().tableName().equals("Customer")
                && context.operationName().equals(OperationName.UPDATE_ITEM)) {
            return WriteModification.builder()
                    .updateExpression(createUpdateExpression())
                    .build();
        }
        return WriteModification.builder().build();  // Return an "empty" WriteModification instance if the extension should not be applied.
                                                     // In this case, if the code is not updating an item on the Customer table.
    }

    private static UpdateExpression createUpdateExpression() {

        // 2. Use a SetAction, a subclass of UpdateAction, to provide the values in the update.
        SetAction setAction =
                SetAction.builder()
                        .path("registrationDate")
                        .value("if_not_exists(registrationDate, :regValue)")
                        .putExpressionValue(":regValue", AttributeValue.fromS(Instant.now().toString()))
                        .build();
        // 3. Build the UpdateExpression with one or more UpdateAction.
        return UpdateExpression.builder()
                .addAction(setAction)
                .build();
    }
}
```

# DynamoDB 拡張クライアント API を非同期的に使用する
<a name="ddb-en-client-async"></a>

アプリケーションで DynamoDB へのノンブロッキングの非同期呼び出しが必要な場合は、[DynamoDbEnhancedAsyncClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedAsyncClient.html) を使用できます。同期実装と似ていますが、次の主な違いがあります。

1. `DynamoDbEnhancedAsyncClient` を構築するときは、次のスニペットに示すように、標準クライアントの非同期バージョン、`DynamoDbAsyncClient` を指定する必要があります。

   ```
    DynamoDbEnhancedAsyncClient enhancedClient = 
        DynamoDbEnhancedAsyncClient.builder()
                                   .dynamoDbClient(dynamoDbAsyncClient)
                                   .build();
   ```

1. 1 つのデータオブジェクトを返すメソッドは、結果だけではなく結果の `CompletableFuture` を返します。そうすることで、アプリケーションは結果をブロックせずに他の処理を行うことができます。次のスニペットは、非同期 `getItem()` メソッドを示しています。

   ```
   CompletableFuture<Customer> result = customerDynamoDbTable.getItem(customer);
   // Perform other work here.
   return result.join();   // Now block and wait for the result.
   ```

1. ページ分割された結果リストを返すメソッドは、同期 `DynamoDbEnhanceClient` が同じメソッドで返す [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/pagination/sync/SdkIterable.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/pagination/sync/SdkIterable.html) の代わりに、[https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/async/SdkPublisher.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/async/SdkPublisher.html) を返します。その後、アプリケーションはハンドラーをそのパブリッシャーにサブスクライブして、結果をブロックせずに非同期的に処理できます。

   ```
   PagePublisher<Customer> results = customerDynamoDbTable.query(r -> r.queryConditional(keyEqualTo(k -> k.partitionValue("Smith"))));
   results.subscribe(myCustomerResultsProcessor);
   // Perform other work and let the processor handle the results asynchronously.
   ```

   `SdkPublisher API` のより詳細な使用例については、このガイドの非同期 `scan()` メソッドについて説明しているセクションの[例](ddb-en-client-use-multirecord.md#ddb-en-client-use-multirecord-scan-async)を参照してください。

# データクラス注釈
<a name="ddb-en-client-anno-index"></a>

次の表は、データクラスで使用できる注釈の一覧と、このガイドの情報と例へのリンクを示しています。テーブルは、注釈名別にアルファベット順に昇順に並べ替えられます。


**このガイドで使用されているデータクラス注釈**  

| 注釈名 | 次に適用される注釈1 | その内容 | このガイドに記載されている場所 | 
| --- | --- | --- | --- | 
| DynamoDbAtomicCounter | 属性2 | レコードがデータベースに書き込まれるたびに、タグ付きの数値属性を増やします。 | [概要とディスカッション。](ddb-en-client-extensions.md#ddb-en-client-extensions-ACE) | 
| DynamoDbAttribute | 属性 | DynamoDB テーブル属性にマップされる Bean プロパティを定義または名前を変更します。 |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/ja_jp/sdk-for-java/latest/developer-guide/ddb-en-client-anno-index.html)  | 
| DynamoDbAutoGeneratedTimestampAttribute | 属性 | 項目がデータベースに正常に書き込まれるたびに、タグ付き属性を現在のタイムスタンプで更新します。 | [概要とディスカッション](ddb-en-client-extensions.md#ddb-en-client-extensions-AGTE)。 | 
| DynamoDbAutoGeneratedUuid | 属性 | 新しいレコードがデータベースに書き込まれるときに、属性の一意の UUID (ユニバーサル一意識別子) を生成します。 | [概要とディスカッション。](ddb-en-client-extensions.md#ddb-en-client-extensions-AGUE) | 
| DynamoDbBean | class | データクラスをテーブルスキーマにマッピング可能としてマークします。 | 「はじめに」セクションの[カスタマークラス](ddb-en-client-gs-tableschema.md#ddb-en-client-gs-tableschema-anno-bean-cust)で初めに使用します。このガイドでは、いくつかの使用法が説明されています。 | 
| DynamoDbConvertedBy | 属性 | カスタム AttributeConverter を注釈付きの属性に関連付けます。 | [最初のディスカッションと例。](ddb-en-client-adv-features-conversion.md#ddb-en-client-adv-features-conversion-single) | 
| DynamoDbFlatten | 属性 | 個別の DynamoDB データクラスのすべての属性をフラット化し、データベースに読み書きされるレコードに最上位の属性として追加します。 |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/ja_jp/sdk-for-java/latest/developer-guide/ddb-en-client-anno-index.html)  | 
| DynamoDbIgnore | 属性 |  その結果、属性はマップされないままの状態となります。  |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/ja_jp/sdk-for-java/latest/developer-guide/ddb-en-client-anno-index.html)  | 
| DynamoDbIgnoreNulls | 属性 | ネストされた DynamoDB オブジェクトの NULL 属性が保存されないようにします。 | [ディスカッションと例。](ddb-en-client-adv-features-ignore-null.md) | 
| DynamoDbImmutable | class |  変更不可能なデータクラスをテーブルスキーマにマッピング可能としてマークします。  |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/ja_jp/sdk-for-java/latest/developer-guide/ddb-en-client-anno-index.html)  | 
| DynamoDbPartitionKey | 属性 |  属性を DynamoDB テーブルのプライマリパーティションキー (ハッシュキー) としてマークします。  |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/ja_jp/sdk-for-java/latest/developer-guide/ddb-en-client-anno-index.html)  | 
| DynamoDbPreserveEmptyObject | 属性 |  注釈付き属性にマップされたオブジェクトにデータが存在しない場合、オブジェクトはすべて NULL フィールドで初期化されるように指定します。  | [ディスカッションと例。](ddb-en-client-adv-features-empty.md) | 
| DynamoDbSecondaryPartitionKey | 属性 |  グローバルセカンダリインデックスのパーティションキーとして属性をマークします。  |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/ja_jp/sdk-for-java/latest/developer-guide/ddb-en-client-anno-index.html)  | 
| DynamoDbSecondarySortKey | 属性 |  属性をグローバルまたはローカルのセカンダリインデックスのオプションのソートキーとしてマークします。  |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/ja_jp/sdk-for-java/latest/developer-guide/ddb-en-client-anno-index.html)  | 
| DynamoDbSortKey | 属性 |  属性をオプションのプライマリソートキー (レンジキー) としてマークします。  |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/ja_jp/sdk-for-java/latest/developer-guide/ddb-en-client-anno-index.html)  | 
| DynamoDbUpdateBehavior | 属性 |  UpdateItem などの「更新」オペレーションの一部としてこの属性が更新されたときの動作を指定します。  | [概要と例。](ddb-en-client-adv-features-upd-behavior.md) | 
| DynamoDbVersionAttribute | 属性 | アイテムのバージョン番号を増やします。 | [概要とディスカッション。](ddb-en-client-extensions.md#ddb-en-client-extensions-VRE) | 

1属性レベルの注釈はゲッターまたはセッターに適用できますが、両方に適用することはできません。このガイドでは、ゲッターの注釈を示します。

2通常、`property` という用語は JavaBean データクラスにカプセル化された値に使用されます。ただし、このガイドでは、DynamoDB で使用されている用語との一貫性を保つため、代わりに `attribute` という用語を使用しています。