

本文為英文版的機器翻譯版本，如內容有任何歧義或不一致之處，概以英文版為準。

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

[DynamoDB](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Introduction.html) 是全受管的 NoSQL 資料庫服務，可提供快速且可預測的效能和無縫的可擴展性。本節說明如何使用 適用於 Java 的 AWS SDK 2.x 使用 DynamoDB。

## 選擇您的 DynamoDB 用戶端
<a name="ddb-clients-overview"></a>

開發套件提供兩種使用 DynamoDB 的主要方法：

低階客戶端 (`DynamoDbClient`)  
提供 DynamoDB 操作的直接存取權，並完全控制請求和回應。當您需要精細控制或使用動態結構描述時，請使用此用戶端。

增強型用戶端 (`DynamoDbEnhancedClient`)  
提供具有 Java 物件和 DynamoDB 項目之間自動映射的物件導向程式設計。也提供文件導向功能，用於處理不遵循固定結構描述的類似 JSON 的資料。使用定義明確的資料模型或文件類型資料時，請使用此用戶端。

## 設定 DynamoDB 用戶端
<a name="ddb-configuration-setup"></a>

使用 DynamoDB 之前，請設定您的用戶端以取得最佳效能和可靠性。

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

DynamoDB 用戶端使用預設的重試計數上限 8，高於其他 AWS 服務 用戶端。此較高的重試計數有助於處理 DynamoDB 的分散式本質和暫時容量限制。如需重試策略的詳細資訊，請參閱 [在 中設定重試行為 AWS SDK for Java 2.x](retry-strategy.md)。

### 使用帳戶型端點最佳化效能
<a name="ddb-account-based-endpoints-v2"></a>

DynamoDB 提供以[AWS 帳戶為基礎的端點](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Programming.SDKOverview.html#Programming.SDKs.endpoints)，透過使用 AWS 您的帳戶 ID 來簡化請求路由來改善效能。

若要使用此功能，您需要 2.28.4 版或更新版本的 AWS SDK for Java 2.x。您可以在 [Maven 中央儲存庫](https://central.sonatype.com/artifact/software.amazon.awssdk/bom/versions)中找到最新版本。支援的 SDK 版本會自動使用新的端點。

若要選擇退出以帳戶為基礎的路由，請選擇下列其中一個選項：
+ 將 DynamoDB 服務用戶端`AccountIdEndpointMode`設定為 `DISABLED`。
+ 設定環境變數。
+ 設定 JVM 系統屬性。
+ 更新共用 AWS 的組態檔案設定。

下列範例示範如何透過設定 DynamoDB 服務用戶端來停用帳戶型路由：

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

如需其他組態選項的詳細資訊，請參閱《 AWS SDKs和工具參考指南》中的[帳戶型端點](https://docs.aws.amazon.com/sdkref/latest/guide/feature-account-endpoints.html)。

## 本主題涵蓋的內容
<a name="ddb-topics-overview"></a>

下列各節說明如何使用 DynamoDB：
+ [在 中使用資料表 DynamoDB](examples-dynamodb-tables.md) - 建立、描述、更新和刪除資料表
+ [在 中使用項目 DynamoDB](examples-dynamodb-items.md) - 新增、擷取和更新個別項目
+ [使用 將 Java 物件映射至 DynamoDB 項目 AWS SDK for Java 2.x](dynamodb-enhanced-client.md) - 搭配增強型用戶端使用物件映射和文件導向資料

如需其他 DynamoDB 程式碼範例，請參閱[程式碼範例程式庫中的 DynamoDB ](java_dynamodb_code_examples.md) AWS 程式碼範例。

# 在 中使用資料表 DynamoDB
<a name="examples-dynamodb-tables"></a>

資料表是 DynamoDB 資料庫中所有項目的容器。您必須先建立資料表 DynamoDB，才能從中新增或移除資料。

對於每個資料表，您必須定義：
+ 您帳戶和區域唯一的資料表*名稱*。
+ 每個值的*主索引鍵*都必須獨一無二，資料表中任兩個項目不能有相同的主索引鍵值。

  主索引鍵可以是*簡單的*，包含單一分割區 (HASH) 索引鍵；也可以是*複合的*，包含分割區和排序 (RANGE) 索引鍵。

  每個索引鍵值都有一個相關聯的*資料類型*，由 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/ScalarAttributeType.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/ScalarAttributeType.html)類別列舉。索引鍵值可以是二進位 (B)、數值 (N)、或字串 (S)。如需詳細資訊，請參閱《 Amazon DynamoDB 開發人員指南》中的[命名規則和資料類型](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html)。
+  *佈建輸送量*值用於定義為資料表預留的讀取/寫入容量單位數。
**注意**  
 [Amazon DynamoDB 定價](https://aws.amazon.com/dynamodb/pricing/)是以您在資料表上設定的佈建輸送量值為基礎，因此請只預留您認為資料表所需的容量。

  資料表的佈建輸送量隨時可以修改，所以您可以在需求變更時調整容量。

## 建立資料表
<a name="dynamodb-create-table"></a>

使用 `DynamoDbClient’s``createTable`方法來建立新的 DynamoDB 資料表。您需要建構資料表屬性和資料表結構描述，這兩項都會用來識別資料表的主索引鍵。您也必須提供初始佈建的輸送量值和資料表名稱。

**注意**  
如果具有您所選名稱的資料表已存在，`[DynamoDbException](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/DynamoDbException.html)`則會擲回 。

### 使用簡單主索引鍵建立資料表
<a name="dynamodb-create-table-simple"></a>

此程式碼會建立一個具有一個屬性的資料表，該屬性是資料表的簡單主索引鍵。 範例使用 `[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>

下列範例會建立具有兩個屬性的資料表。這兩個屬性都用於複合主索引鍵。

 **匯入** 

```
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!");
    }
```

根據預設，每次呼叫最多傳回 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) 物件，包含該項目的所有屬性。您可以在 [ 中指定一或多個](https://docs.aws.amazon.com//amazondynamodb/latest/developerguide/Expressions.ProjectionExpressions.html)投射運算式`GetItemRequest`來擷取特定的屬性。

您可以使用所傳回 `GetItemResponse` 物件的 `item()` 方法來擷取與項目關聯之索引鍵 (字串) 和值的[對應](https://docs.oracle.com/javase/8/docs/api/index.html?java/util/Map.html) ([AttributeValue](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/AttributeValue.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) 物件。

您可以傳回一個[集合](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 增強型用戶端 API](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/package-summary.html) 是高階程式庫，是適用於 Java 的 SDK v1.x 中 `DynamoDBMapper`類別的後續版本。提供將客戶端類別映射到 DynamoDB 資料表的直接方式。您可以在程式碼中定義資料表與其對應資料類別之間的關係。定義關係之後，您可以直觀地對 DynamoDB 中的資料表或項目執行各種建立、讀取、更新或刪除 (CRUD) 操作。

DynamoDB 增強型用戶端 API 也包含[增強型文件 API](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/document/package-summary.html)，可讓您使用未遵循定義結構描述的文件類型項目。

**Topics**
+ [開始使用 DynamoDB 增強型用戶端 API](ddb-en-client-getting-started.md)
+ [了解 DynamoDB 增強型用戶端 API 的基本概念](ddb-en-client-use.md)
+ [使用進階映射功能](ddb-en-client-adv-features.md)
+ [使用適用於 DynamoDB 的增強型文件 API 處理 JSON 文件](ddb-en-client-doc-api.md)
+ [使用擴充功能自訂 DynamoDB 增強型用戶端操作](ddb-en-client-extensions.md)
+ [非同步使用 DynamoDB 增強型用戶端 API](ddb-en-client-async.md)
+ [資料類別註釋](ddb-en-client-anno-index.md)

# 開始使用 DynamoDB 增強型用戶端 API
<a name="ddb-en-client-getting-started"></a>

以下教學課程向您介紹使用 DynamoDB 增強型用戶端 API 所需的基本概念。

## 新增相依性
<a name="ddb-en-client-gs-dep"></a>

若要開始在專案中使用 DynamoDB 增強型用戶端 API，請在 `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 中央儲存庫執行[最新版本](https://central.sonatype.com/artifact/software.amazon.awssdk/bom)的搜尋，並以此值取代 *<VERSION>*。

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

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

對 Maven 中央儲存庫執行[最新版本](https://central.sonatype.com/artifact/software.amazon.awssdk/bom)的搜尋，並以此值取代 *<VERSION>*。

------

# `TableSchema` 從資料類別產生
<a name="ddb-en-client-gs-tableschema"></a>

`[TableSchema](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/TableSchema.html)` 可讓增強型用戶端在用戶端類別之間映射 DynamoDB 屬性值。在本教學課程中，您將了解衍生自靜態資料類別並使用建置器從程式碼產生的 `TableSchema`。

## 使用標註的資料類別
<a name="ddb-en-client-gs-tableschema-anno-bean"></a>

適用於 Java 的 SDK 2.x 包含[一組註釋](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/mapper/annotations/package-summary.html)，您可以搭配資料類別使用，以快速產生 `TableSchema`，將您的類別映射至資料表。

首先建立符合 [JavaBean 規格](https://download.oracle.com/otn-pub/jcp/7224-javabeans-1.01-fr-spec-oth-JSpec/beans.101.pdf)的資料類別。規格要求 類別具有無引數的公有建構函數，並具有類別中每個屬性的 getter 和 setter。包含類別層級註釋，表示資料類別是 `DynamoDbBean`。此外，在 getter 或 setter 上至少包含主索引鍵屬性的`DynamoDbPartitionKey`註釋。

您可以將[屬性層級註釋](ddb-en-client-anno-index.md)套用至 getter 或 setter，但不能同時套用兩者。

**注意**  
此術語`property`通常用於封裝在 JavaBean 中的值。不過，本指南會`attribute`改用 一詞，以符合 DynamoDB 所使用的術語。

下列`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、地圖、清單和集合等屬性](ddb-en-client-adv-features-nested.md)區段中的 `Person`類別。

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

如果您在程式碼中定義資料表結構描述，則可以略過 Bean 自我檢查的成本。如果您編寫結構描述的程式碼，您的類別就不需要遵循 JavaBean 命名標準，也不需要加上註釋。下列範例使用建置器，相當於使用註釋的`Customer`類別範例。

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

# 建立增強型用戶端和 `DynamoDbTable`
<a name="ddb-en-client-getting-started-dynamodbTable"></a>

## 建立增強型用戶端
<a name="ddb-en-client-getting-started-dynamodbTable-eclient"></a>

[DynamoDbEnhancedClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedClient.html) 類別或其非同步對應 [DynamoDbEnhancedAsyncClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedAsyncClient.html) 是使用 DynamoDB 增強型用戶端 API 的進入點。

增強型用戶端需要標準 `[DynamoDbClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/DynamoDbClient.html)`才能執行工作。API 提供兩種建立`DynamoDbEnhancedClient`執行個體的方式。下列程式碼片段中顯示的第一個選項會建立標準`DynamoDbClient`，其中包含從組態設定中挑選的預設設定。

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

如果您想要設定基礎標準用戶端，您可以將其提供給增強型用戶端的建置器方法，如下列程式碼片段所示。

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

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

## 建立 `DynamoDbTable` 執行個體
<a name="ddb-en-client-getting-started-dynamodbTable-table"></a>

將 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbTable.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbTable.html)視為 DynamoDB 資料表的用戶端表示法，該資料表使用 提供的映射功能`TableSchema`。`DynamoDbTable` 類別提供 CRUD 操作的方法，可讓您與單一 DynamoDB 資料表互動。

`DynamoDbTable<T>` 是採用單一類型引數的一般類別，無論是自訂類別，還是使用文件類型項目`EnhancedDocument`時的 。此引數類型會建立您使用的類別與單一 DynamoDB 資料表之間的關係。

使用 的`table()`原廠方法`DynamoDbEnhancedClient`建立`DynamoDbTable`執行個體，如下列程式碼片段所示。

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

`DynamoDbTable` 執行個體是單調的候選項目，因為它們不可變，可用於整個應用程式。

您的程式碼現在具有可使用`Customer`執行個體的 DynamoDB 資料表的記憶體內表示法。實際的 DynamoDB 資料表可能存在，也可能不存在。如果名為 的資料表`Customer`已存在，您可以開始對其執行 CRUD 操作。如果不存在，請使用`DynamoDbTable`執行個體來建立資料表，如下一節所述。

# 視需要建立 DynamoDB 資料表
<a name="ddb-en-client-gs-ddbtable"></a>

建立`DynamoDbTable`執行個體之後，請使用它在 DynamoDB 中執行資料表的*一次性*建立。

## 建立資料表範例程式碼
<a name="ddb-en-client-gs-ddbtable-createex"></a>

下列範例會根據`Customer`資料類別建立 DynamoDB 資料表。

此範例會使用與類別名稱`Customer`相同的名稱建立 DynamoDB 資料表，但資料表名稱可以是其他名稱。無論您如何命名資料表，您都必須在其他應用程式中使用此名稱，才能使用資料表。每當您建立另一個`DynamoDbTable`物件時，請將此名稱提供給 `table()`方法，以便使用基礎 DynamoDB 資料表。

Java lambda 參數 `builder`傳遞至 `createTable`方法可讓您[自訂資料表](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;
```

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

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

**注意**  
從資料類別產生資料表時，DynamoDB 資料表的屬性名稱會以小寫字母開頭。如果您希望資料表的屬性名稱以大寫字母開頭，請使用 [`@DynamoDbAttribute(NAME)`註釋](ddb-en-client-adv-features-inex-attr.md)，並提供您想要做為參數的名稱。

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

建立資料表之後，請使用`DynamoDbTable`執行個體對 DynamoDB 資料表執行操作。

在下列範例中，單一項目`DynamoDbTable<Customer>`會與[`Customer`資料類別](ddb-en-client-gs-tableschema.md#ddb-en-client-gs-tableschema-anno-bean-cust)執行個體一起做為參數傳遞，以將新項目新增至資料表。

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

## `Customer` 物件
<a name="perform_ops_create_customer_instatnce"></a>

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

將`customer`物件傳送至 DynamoDB 服務之前，請記錄物件`toString()`方法的輸出，將其與增強型用戶端傳送的內容進行比較。

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

線路層級記錄會顯示所產生請求的承載。增強型用戶端會從資料類別產生低階表示。`regDate` 屬性是 Java 中的`Instant`類型，以 DynamoDB 字串表示。

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

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

上一節說明如何從 Java 資料類別開始建立 DynamoDB 資料表。如果您已經有現有的資料表，並想要使用增強型用戶端的功能，您可以建立 Java 資料類別來使用資料表。您需要檢查 DynamoDB 資料表，並將必要的註釋新增至資料類別。

在使用現有資料表之前，請呼叫 `DynamoDbEnhanced.table()`方法。這在上一個範例中使用下列陳述式完成。

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

傳回`DynamoDbTable`執行個體之後，您可以立即開始使用基礎資料表。您不需要透過呼叫 `DynamoDbTable.createTable()`方法重新建立資料表。

下列範例示範如何立即從 DynamoDB 資料表擷取`Customer`執行個體。

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

**重要**  
`table()` 方法中使用的資料表名稱必須符合現有的 DynamoDB 資料表名稱。

# 了解 DynamoDB 增強型用戶端 API 的基本概念
<a name="ddb-en-client-use"></a>

本主題討論 DynamoDB 增強型用戶端 API 的基本功能，並將其與[標準 DynamoDB 用戶端 API](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/package-summary.html) 進行比較。

如果您是初次使用 DynamoDB 增強型用戶端 API，我們建議您瀏覽[簡介教學](ddb-en-client-getting-started.md)課程，熟悉基礎課程。

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

DynamoDB 資料表存放項目。根據您的使用案例，Java 端的項目可以採用靜態結構化資料或動態建立的結構形式。

如果您的使用案例呼叫具有一組一致屬性的項目，請使用[註釋類別](ddb-en-client-gs-tableschema.md#ddb-en-client-gs-tableschema-anno-bean)或使用[建置器](ddb-en-client-gs-tableschema.md#ddb-en-client-gs-tableschema-builder)來產生適當的靜態類型 `TableSchema`。

或者，如果您需要存放由不同結構組成的項目，請建立 `DocumentTableSchema`。 `DocumentTableSchema`是[增強型文件 API](ddb-en-client-doc-api.md) 的一部分，只需要靜態類型的主索引鍵，並使用`EnhancedDocument`執行個體來保存資料元素。另一個[主題涵蓋了增強型文件 API。](ddb-en-client-doc-api.md)

## 資料模型類別的屬性類型
<a name="ddb-en-client-use-types"></a>

雖然 DynamoDB 相較於 Java 的豐富類型系統支援[少量屬性類型](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.NamingRulesDataTypes.html#HowItWorks.DataTypes)，但 DynamoDB 增強型用戶端 API 提供將 Java 類別成員轉換為 DynamoDB 屬性類型的機制。

Java 資料類別的屬性類型 （屬性） 應為物件類型，而非基本類型。例如， 一律使用 `Long`和 `Integer` 物件資料類型，而不是 `int` `long`和 基本資料類型。

根據預設，DynamoDB 增強型用戶端 API 支援多種類型的屬性轉換器，例如 [Integer](https://docs.oracle.com/javase/8/docs/api/java/lang/Integer.html)、[String](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) 和 [Instant](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))，您可以採取兩種方法。
+ 如果您可以存取來源檔案，則可以使用 `@DynamoDbBean`（或 ) 註釋 類別`@DynamoDbImmutable`。討論巢狀屬性的 區段顯示使用註釋類別[的範例](ddb-en-client-adv-features-nested.md#ddb-en-client-adv-features-nested-map-anno)。
+ 如果 無法存取 屬性的 JavaBean 資料類別來源檔案 （或者您不想註釋您有權存取之類別的來源檔案），則可以使用建置器方法。這會在不定義索引鍵的情況下建立資料表結構描述。然後，您可以在另一個資料表結構描述內將此資料表結構描述巢狀化，以執行映射。巢狀屬性區段[的範例](ddb-en-client-adv-features-nested.md#ddb-en-client-adv-features-nested-map-builder)顯示巢狀結構描述的使用。

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

當您使用 `putItem`方法時，增強型用戶端不會在對 DynamoDB 的請求中包含映射資料物件的 null 值屬性。

SDK 的`updateItem`請求預設行為會從 DynamoDB 中的項目中移除屬性，該項目在方法中提交的 物件中設定為 null`updateItem`。如果您想要更新一些屬性值並保持其他屬性值不變，您有兩個選項。
+ 在您變更值之前擷取項目 （使用 `getItem`)。透過使用此方法，開發套件會將所有更新值和舊值提交至 DynamoDB。
+ 當您建置更新項目的請求`IgnoreNullsMode.MAPS_ONLY`時，請使用 `[IgnoreNullsMode](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/IgnoreNullsMode.html).SCALAR_ONLY`或 。兩種模式都會忽略代表 DynamoDB 中純量屬性之物件中的 null 值屬性。本指南中的[更新包含複雜類型的項目](ddb-en-client-adv-features-nested.md#ddb-en-client-adv-features-nested-updates)主題包含有關`IgnoreNullsMode`值以及如何使用複雜類型的詳細資訊。

下列範例示範 `updateItem()`方法`ignoreNullsMode()`的 。

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

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

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

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

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

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


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

## DynamoDB 增強型用戶端基本方法
<a name="ddb-en-client-use-basic-ops"></a>

增強型用戶端的基本方法會映射到其命名所用的 DynamoDB 服務操作。下列範例顯示每個方法的最簡單變化。您可以透過傳入增強型請求物件來自訂每個方法。增強型請求物件提供標準 DynamoDB 用戶端中大多數可用的功能。它們完全記錄在 AWS SDK for Java 2.x API 參考中。

此範例使用先前[`Customer` 類別](ddb-en-client-gs-tableschema.md#ddb-en-client-gs-tableschema-anno-bean-cust)顯示的 。

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

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

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

// PutItem
customerTable.putItem(customer);

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

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

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

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

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

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

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

## 比較 DynamoDB 增強型用戶端與標準 DynamoDB 用戶端
<a name="ddb-en-client-use-compare"></a>

DynamoDB 用戶端 APIs — [標準](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/package-summary.html)和[增強](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/package-summary.html)型 — 可讓您使用 DynamoDB 資料表來執行 CRUD （建立、讀取、更新和刪除） 資料層級操作。用戶端 APIs 之間的差異在於其完成方式。使用標準用戶端，您可以直接使用低階資料屬性。增強型用戶端 API 使用熟悉的 Java 類別，並映射到幕後的低階 API。

雖然兩個用戶端 APIs 都支援資料層級操作，但標準 DynamoDB 用戶端也支援資源層級操作。資源層級操作會管理資料庫，例如建立備份、列出資料表和更新資料表。增強型用戶端 API 支援選取數量的資源層級操作，例如建立、描述和刪除資料表。

為了說明兩個用戶端 APIs 使用的不同方法，下列程式碼範例顯示使用標準用戶端和增強型用戶端建立相同的`ProductCatalog`資料表。

### 比較：使用標準 DynamoDB 用戶端建立資料表
<a name="ddb-en-client-use-compare-cs1"></a>

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

### 比較：使用 DynamoDB 增強型用戶端建立資料表
<a name="ddb-en-client-use-compare-cs2"></a>

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

增強型用戶端使用下列標註的資料類別。DynamoDB 增強型用戶端會將 Java 資料類型映射至 DynamoDB 資料類型，以取得較不詳細的程式碼，更容易遵循。 `ProductCatalog` 是搭配 DynamoDB 增強型用戶端使用不可變類別的範例。本主題[稍後將討論](ddb-en-client-use-immut.md)對映射資料類別使用不可變類別。

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

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

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

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

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


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

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

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


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

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

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

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

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

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

以下兩個批次寫入的程式碼範例說明使用標準用戶端而非增強型用戶端時的詳細程度和缺乏類型安全性。

### 比較：使用標準 DynamoDB 用戶端進行批次寫入
<a name="ddb-en-client-use-compare-cs4"></a>

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

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

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

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

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

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

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

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

### 比較：使用 DynamoDB 增強型用戶端進行批次寫入
<a name="ddb-en-client-use-compare-cs5"></a>

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

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

# 使用不可變的資料類別
<a name="ddb-en-client-use-immut"></a>

DynamoDB 增強型用戶端 API 的映射功能適用於不可變的資料類別。不可變類別只有取得器，且需要 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 資料表屬性的 getter。

1. 每個 getter 在建置器類別上都必須有對應的區分大小寫設定器。

1. 僅必須符合下列其中一項建構條件。
   + 建置器類別必須具有公有預設建構函數。
   + 資料類別必須具有名為 的公有靜態方法`builder()`，不採用任何參數，並傳回建置器類別的執行個體。此選項會顯示在不可變類別中`Customer`。

1.  建置器類別必須具有名為 的公有方法`build()`，不採用任何參數，並傳回不可變類別的執行個體。

若要`TableSchema`為您的不可變類別建立 ，請使用 上的 `fromImmutableClass()`方法`TableSchema`，如下列程式碼片段所示。

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

就像您可以從可變類別建立 DynamoDB 資料表一樣，您可以從不可變類別建立一個資料表，並對 `createTable()` 進行*一次性*呼叫`DynamoDbTable`，如下列程式碼片段範例所示。

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

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

第三方程式庫，例如 [Project Lombok](https://projectlombok.org/)，可協助產生與不可變物件相關聯的樣板程式碼。只要資料類別遵循本節中詳述的慣例，DynamoDB 增強型用戶端 API 就會使用這些程式庫。

下列範例顯示具有 Lombok 註釋的不可變`CustomerImmutable`類別。請注意，Lombok `onMethod`的功能如何將屬性型 DynamoDB 註釋，例如 `@DynamoDbPartitionKey`，複製到產生的程式碼。

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

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

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

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

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

DynamoDB 增強型用戶端 API 中的表達式是 [DynamoDB 表達式](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.html)的 Java 表示法。

DynamoDB 增強型用戶端 API 使用三種類型的表達式：

[運算式](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/Expression.html)  
當您定義條件和篩選條件時，會使用 `Expression`類別。

[https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/QueryConditional.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/QueryConditional.html)  
這種類型的表達式代表查詢操作的關鍵[條件](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Query.html#Query.KeyConditionExpressions)。

[https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/update/UpdateExpression.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/update/UpdateExpression.html)  
此類別可協助您撰寫 DynamoDB [更新表達式](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html)，且目前在更新項目時用於延伸架構。

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

表達式由下列項目組成：
+ 字串表達式 （必要）。字串包含 DynamoDB 邏輯表達式，其中包含屬性名稱和屬性值的預留位置名稱。
+ 表達式值的映射 （通常為必要）。
+ 表達式名稱的映射 （選用）。

使用建置器產生`Expression`採用下列一般形式的物件。

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

`Expression`通常需要表達式值的映射。映射提供字串表達式中預留位置的值。映射金鑰包含前面加上冒號 (`:`) 的預留位置名稱，而映射值是 [AttributeValue](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/AttributeValue.html) 的執行個體。[AttributeValues](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/internal/AttributeValues.html) 類別具有從常值產生`AttributeValue`執行個體的便利方法。或者，您可以使用 `AttributeValue.Builder`來產生`AttributeValue`執行個體。

下列程式碼片段顯示註解行 2 之後有兩個項目的映射。傳遞至 `expression()`方法的字串，顯示於註解行 1 之後，包含 DynamoDB 在執行操作之前解析的預留位置。此程式碼片段不包含表達式名稱的映射，因為*價格*是允許的屬性名稱。

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

如果 DynamoDB 資料表中的屬性名稱是保留字、以數字開頭或包含空格，則 需要表達式名稱的映射`Expression`。

例如，如果屬性名稱`1price`不是`price`在先前的程式碼範例中，則需要修改範例，如下列範例所示。

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

表達式名稱的預留位置以井號 () 開頭`#`。表達式名稱映射的項目會使用預留位置做為索引鍵，並使用屬性名稱做為值。映射會使用 `expressionNames()`方法新增至表達式建置器。DynamoDB 會在執行操作之前解析屬性名稱。

如果在字串表達式中使用函數，則不需要表達式值。表達式函數的範例為 `attribute_exists(<attribute_name>)`。

下列範例會建置`Expression`使用 [DynamoDB 函數](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html#Expressions.OperatorsAndFunctions.Functions)的 。此範例中的表達式字串不使用任何預留位置。此表達式可用於 `putItem`操作，以檢查資料庫中是否存在屬性`movie`值等於資料物件`movie`屬性的項目。

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

DynamoDB 開發人員指南包含與 DynamoDB 搭配使用的[低階表達式](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.html)的完整資訊。

## 條件表達式和條件式
<a name="ddb-en-client-expressions-cond"></a>

當您使用 `putItem()`、 `updateItem()`和 `deleteItem()`方法，以及使用交易和批次操作時，您可以使用 `[Expression](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/Expression.html)` 物件來指定 DynamoDB 必須滿足的條件，才能繼續操作。這些表達式會命名為條件表達式。如需範例，請參閱本指南中所示[交易範例](ddb-en-client-use-multiop-trans.md#ddb-en-client-use-multiop-trans-writeitems-opcondition)的 `addDeleteItem()`方法 （註解行 1 之後） 中使用的條件表達式。

當您使用 `query()` 方法時，條件會以 表示[https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/QueryConditional.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/QueryConditional.html)。`QueryConditional` 類別有數種靜態便利方法，可協助您撰寫判斷要從 DynamoDB 讀取哪些項目的條件。

如需 的範例`QueryConditionals`，請參閱本指南 [`Query` 方法範例](ddb-en-client-use-multirecord.md#ddb-en-client-use-multirecord-query-example)一節的第一個程式碼範例。

## 篩選條件表達式
<a name="ddb-en-client-expressions-filter"></a>

篩選條件表達式用於掃描和查詢操作，以篩選傳回的項目。

從資料庫讀取所有資料後，就會套用篩選條件表達式，因此讀取成本與沒有篩選條件相同。*Amazon DynamoDB 開發人員指南*提供有關使用篩選條件表達式進行[查詢](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Query.html#Query.FilterExpression)和[掃描](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Scan.html#Scan.FilterExpression)操作的詳細資訊。

下列範例顯示新增至掃描請求的篩選條件表達式。條件會將傳回的項目限制為價格介於 8.00 到 80.00 之間的項目。

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

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

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

DynamoDB 增強型用戶端的 `updateItem()` 方法提供標準方法來更新 DynamoDB 中的項目。不過，當您需要更多功能時，[UpdateExpressions](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/update/UpdateExpression.html) 會提供 DynamoDB [更新表達式語法的安全類型表示法。](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html)例如，您可以使用 `UpdateExpressions` 來增加值，而無需先從 DynamoDB 讀取項目，或將個別成員新增至清單。更新表達式目前可在 `updateItem()`方法的自訂擴充功能中使用。

如需使用更新表達式的範例，請參閱本指南中的[自訂延伸模組範例](ddb-en-client-extensions-custom.md)。

如需更新表達式的詳細資訊，請參閱《[Amazon DynamoDB 開發人員指南](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html)》。

# 使用分頁結果：掃描和查詢
<a name="ddb-en-client-use-multirecord"></a>

DynamoDB 增強型用戶端 API 的 `scan``query`和 `batch`方法會傳回具有一或多個*頁面*的回應。頁面包含一或多個項目。您的程式碼可以根據每一頁處理回應，也可以處理個別項目。

同步`DynamoDbEnhancedClient`用戶端傳回的分頁回應會傳回 [PageIterable](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/PageIterable.html) 物件，而非同步傳回的回應則會`DynamoDbEnhancedAsyncClient`傳回 [PagePublisher](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/PagePublisher.html) 物件。

本節會查看處理分頁結果，並提供使用掃描和查詢 APIs的範例。

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

SDK 的 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbAsyncTable.html#scan(java.util.function.Consumer)](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbAsyncTable.html#scan(java.util.function.Consumer))方法對應至相同名稱的 [DynamoDB 操作](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Scan.html)。DynamoDB 增強型用戶端 API 提供相同的選項，但它使用熟悉的物件模型並為您處理分頁。

首先，我們透過查看同步映射類別 [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 之後， `PageIterable` 物件 `pagedResults`會由 `scan`方法傳回。的 `stream`方法會`PageIterable`傳回[https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html](https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html)物件，您可以用來處理頁面。在此範例中，會計算並記錄頁數。

從註解行 4 開始，範例顯示存取`ProductCatalog`項目的兩種變化。註解行 4a 後的版本會串流瀏覽每個頁面，並排序和記錄每個頁面上的項目。註解行 4b 後的版本會略過頁面反覆運算，並直接存取項目。

由於 和 兩個父介面[https://docs.oracle.com/javase/8/docs/api/java/lang/Iterable.html](https://docs.oracle.com/javase/8/docs/api/java/lang/Iterable.html)，`PageIterable`介面提供多種處理結果的方法[https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/pagination/sync/SdkIterable.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/pagination/sync/SdkIterable.html)。 `Iterable`帶來 `forEach`、 `iterator`和 `spliterator`方法， `SdkIterable`帶來 `stream`方法。

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

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

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

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

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

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

非同步`scan`方法會將結果傳回為`PagePublisher`物件。`PagePublisher` 界面有兩種`subscribe`方法來處理回應頁面。一種`subscribe`方法來自`org.reactivestreams.Publisher`父界面。若要使用此第一個選項處理頁面，請將`[Subscriber](https://www.reactive-streams.org/reactive-streams-1.0.0-javadoc/org/reactivestreams/Subscriber.html)`執行個體傳遞至 `subscribe`方法。以下的第一個範例顯示 `subscribe`方法的使用方式。

第二個`subscribe`方法來自 [SdkPublisher](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/async/SdkPublisher.html) 界面。此版本的 `subscribe`接受 [https://docs.oracle.com/javase/8/docs/api/java/util/function/Consumer.html](https://docs.oracle.com/javase/8/docs/api/java/util/function/Consumer.html)而非 `Subscriber`。此`subscribe`方法變化會顯示在以下第二個範例中。

下列範例顯示使用上一個範例中所示相同篩選條件表達式的 `scan`方法的非同步版本。

在註解行 3 之後， `DynamoDbAsyncTable.scan` 會傳回`PagePublisher`物件。在下一行，程式碼會建立`org.reactivestreams.Subscriber`界面的執行個體 `ProductCatalogSubscriber`，其會在註解行 4 `PagePublisher`之後訂閱 。

`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 lambda 參數會使用頁面，進一步處理每個項目。在此範例中，會處理每個頁面，然後對每個頁面上的項目進行排序和記錄。

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

的 `items`方法會`PagePublisher`取消包裝模型執行個體，讓您的程式碼可以直接處理項目。此方法會顯示在下列程式碼片段中。

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

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

您可以使用 DynamoDB 增強型用戶端來查詢資料表，並擷取符合特定條件的多個項目。[https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbTable.html#query(software.amazon.awssdk.enhanced.dynamodb.model.QueryEnhancedRequest)](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbTable.html#query(software.amazon.awssdk.enhanced.dynamodb.model.QueryEnhancedRequest)) 方法會根據主索引鍵值，使用資料類別上定義的 `@DynamoDbPartitionKey`和選用`@DynamoDbSortKey`註釋來尋找項目。

`query()` 方法需要分割區索引鍵值，並選擇性地接受排序索引鍵條件，以進一步精簡結果。如同 `scan` API，查詢會`PageIterable`針對同步呼叫傳回 ，`PagePublisher`針對非同步呼叫傳回 。

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

以下`query()`的方法程式碼範例使用 `MovieActor`類別。資料類別會定義複合主索引鍵，該索引鍵由 **`movie`** 屬性做為分割區索引鍵，而**`actor`**屬性做為排序索引鍵。

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

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

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

import java.util.Objects;

@DynamoDbBean
public class MovieActor implements Comparable<MovieActor> {

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

針對下列項目查詢之後的程式碼範例。

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

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

下列程式碼定義兩個`QueryConditional`執行個體： `keyEqual`（在註解行 1 之後） 和 `sortGreaterThanOrEqualTo`（在註解行 1a 之後）。

#### 依分割區索引鍵查詢項目
<a name="keyEqual-query-conditional-example"></a>

`keyEqual` 執行個體會比對分割區索引鍵值為 的項目**`movie01`**。

此範例也會在註解行 2 之後定義篩選條件表達式，以篩選掉任何沒有 **`actingschoolname`**值的項目。

`QueryEnhancedRequest` 結合了查詢的索引鍵條件和篩選條件表達式。

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

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

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

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

**Example – 使用`keyEqual`查詢條件式輸出**  
以下是執行 方法的輸出。輸出會顯示`movieName`值為 **movie01** 的項目，且不會顯示`actingSchoolName`等於 的項目**`null`**。  

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

#### 依分割區索引鍵和排序索引鍵查詢項目
<a name="sort-type-query-conditional-example"></a>

透過為大於或等於 **actor2** 的值新增排序索引鍵條件， 會`sortGreaterThanOrEqualTo``QueryConditional`精簡分割區索引鍵比對 (**movie01**)。

開頭為 [`QueryConditional`的方法](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/QueryConditional.html)`sort`需要分割區索引鍵值才能比對，並根據排序索引鍵值進行比較，進一步精簡查詢。在方法名稱`Sort`中， 並不表示結果會排序，但排序索引鍵值將用於比較。

在下列程式碼片段中，我們會變更先前在註解行 3 之後顯示的查詢請求。此程式碼片段會將「keyEqual」查詢條件取代為註解行 1a 之後定義的「sortGreaterThanOrEqualTo」查詢條件。下列程式碼也會移除篩選條件表達式。

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

**Example – 使用`sortGreaterThanOrEqualTo`查詢條件式輸出**  
下列輸出會顯示查詢的結果。查詢會傳回`movieName`值等於 **movie01** 的項目，且只有`actorName`值大於或等於 **actor2** 的項目。由於我們移除篩選條件，查詢會傳回屬性沒有值的項目`actingSchoolName`。  

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

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

DynamoDB 增強型用戶端 API 提供兩種批次方法：[`batchGetItem`()](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedClient.html#batchGetItem(java.util.function.Consumer)) 和 [`batchWriteItem`()](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedClient.html#batchWriteItem(java.util.function.Consumer))。

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

使用 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedClient.html#batchGetItem(java.util.function.Consumer)](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedClient.html#batchGetItem(java.util.function.Consumer))方法，您可以在一個整體請求中跨多個資料表擷取最多 100 個個別項目。下列範例使用先前顯示的 [`Customer`](ddb-en-client-gs-tableschema.md#ddb-en-client-gs-tableschema-anno-bean-cust)和 [`MovieActor`](ddb-en-client-use-multirecord.md#ddb-en-client-use-movieactor-class) 資料類別。

在第 1 行和第 2 行之後的範例中，您會建置稍後在註解第 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 所示。開發套件會在提交請求之前擷取幕後的索引鍵值。

當您使用金鑰型方法指定項目時，如 2a 之後的兩個陳述式所示，您也可以指定 DynamoDB 應執行[高度一致讀取](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/HowItWorks.ReadConsistency.html)。使用 `consistentRead()`方法時，必須在相同資料表的所有請求項目上使用。

若要擷取 DynamoDB 找到的項目，請使用註解行 4 後顯示`[resultsForTable() ](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/BatchGetResultPage.html#resultsForTable(software.amazon.awssdk.enhanced.dynamodb.MappedTableResource))`的方法。針對請求中讀取的每個資料表呼叫 方法。 會`resultsForTable()`傳回可使用任何`java.util.List`方法處理的找到項目清單。此範例會記錄每個項目。

若要探索 DynamoDB 未處理的項目，請在註解行 5 之後使用 方法。`BatchGetResultPage` 類別具有 `[unprocessedKeysForTable()](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/BatchGetResultPage.html#unprocessedKeysForTable(software.amazon.awssdk.enhanced.dynamodb.MappedTableResource))`方法，可讓您存取每個未處理的金鑰。[BatchGetItem API 參考](https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_BatchGetItem.html)提供有關導致未處理項目之情況的詳細資訊。

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

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

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

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

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

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

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

在執行範例程式碼之前，假設兩個資料表中有下列項目。

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

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

下列輸出顯示註解行 4 之後傳回和記錄的項目。

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

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

`batchWriteItem()` 方法會在一或多個資料表中放置或刪除多個項目。您可以在請求中指定最多 25 個個別放置或刪除操作。下列範例使用先前顯示的 [`ProductCatalog`](ddb-en-client-use.md#ddb-en-client-use-compare-cs3)和 [`MovieActor`](ddb-en-client-use-multirecord.md#ddb-en-client-use-movieactor-class) 模型類別。

`WriteBatch` 物件是在註解行 1 和 2 之後建置。對於`ProductCatalog`資料表，程式碼會放置一個項目並刪除一個項目。對於註解行 2 之後的`MovieActor`資料表，程式碼會放置兩個項目並刪除一個項目。

`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 屬性值，則會移除資料庫中的值。這就是`Blue Jasmine`電影項目產生的 `actingSchoolName` 為 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()`方法。適用於 Java 的 SDK 的交易方法在 DynamoDB 資料表中提供原子性、一致性、隔離性和耐久性 (ACID)，協助您在應用程式中維持資料正確性。

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

`[transactGetItems()](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedClient.html#transactGetItems(java.util.function.Consumer))` 方法最多接受 100 個個別的項目請求。所有項目都會在單一原子交易中讀取。*Amazon DynamoDB 開發人員指南*提供有關[導致`transactGetItems()`方法失敗的條件](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/transaction-apis.html#transaction-apis-txgetitems)，以及呼叫 時所用隔離層級的資訊`[transactGetItem()](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/transaction-apis.html#transaction-isolation)`。

在下列範例中的註解行 1 之後，程式碼會使用 `[builder](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/TransactGetItemsEnhancedRequest.Builder.html)` 參數呼叫 `transactGetItems()`方法。建置器的 `[addGetItem()](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/TransactGetItemsEnhancedRequest.Builder.html#addGetItem(software.amazon.awssdk.enhanced.dynamodb.MappedTableResource,T))`會使用包含 SDK 將用於產生最終請求之金鑰值的資料物件叫用三次。

請求會在註解行 2 之後傳回`[Document](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/Document.html)`物件清單。傳回的文件清單包含與請求順序相同的項目資料非空[的文件](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/Document.html)執行個體。如果傳回項目資料，`[Document.getItem(MappedTableResource<T> mappedTableResource)](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/Document.html#getItem(software.amazon.awssdk.enhanced.dynamodb.MappedTableResource))`方法會將未輸入`Document`物件轉換為類型 Java 物件，否則方法會傳回 null。

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

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

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

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

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

DynamoDB 資料表在程式碼範例執行之前包含下列項目。

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

系統會記錄下列輸出。如果請求項目但找不到，則不會傳回，因為名為 之電影請求的情況。 `Blue Jasmine`

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

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

在跨多個資料表的單一原子交易中， `[transactWriteItems()](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedClient.html#transactWriteItems(java.util.function.Consumer))` 接受最多 100 個放置、更新或刪除動作。*Amazon DynamoDB 開發人員指南*包含[基礎 DynamoDB 服務操作](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/transaction-apis.html#transaction-apis-txwriteitems)之限制和失敗條件的詳細資訊。

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

在下列範例中，兩個資料表需要四個操作。對應的模型類別 [`ProductCatalog`](ddb-en-client-use.md#ddb-en-client-use-compare-cs3)和 [`MovieActor`](ddb-en-client-use-multirecord.md#ddb-en-client-use-movieactor-class) 先前已顯示。

三個可能的操作 - 輸入、更新和刪除 - 都使用專用請求參數來指定詳細資訊。

註解行 1 後的程式碼顯示 `addPutItem()`方法的簡單變化。方法接受`[MappedTableResource](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/MappedTableResource.html)`物件和要放置的資料物件執行個體。註解行 2 後的陳述式會顯示接受`[TransactPutItemEnhancedRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/TransactPutItemEnhancedRequest.html)`執行個體的變化。此變化可讓您在請求中新增更多選項，例如條件表達式。後續[範例](#ddb-en-client-use-multiop-trans-writeitems-opcondition)顯示個別操作的條件表達式。

在註解行 3 之後請求更新操作。 `[TransactUpdateItemEnhancedRequest](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/TransactUpdateItemEnhancedRequest.Builder.html)` 具有 `ignoreNulls()`方法，可讓您設定 SDK 對模型物件上的`null`值執行的操作。如果 `ignoreNulls()` 方法傳回 true，則 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>

下列範例顯示使用條件檢查。條件檢查用於檢查項目是否存在，或檢查資料庫中項目特定屬性的條件。在條件檢查中檢查的項目無法在交易中的另一個操作中使用。

**注意**  
在同一筆交易中，您無法針對同一個項目進行多項操作。例如，您無法執行條件檢查，也無法嘗試更新相同交易中的相同項目。

此範例顯示交易寫入項目請求中的每種操作類型之一。在註解行 2 之後，如果 `conditionExpression` 參數評估為 ，則 `addConditionCheck()`方法會提供交易失敗的條件`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'}
```

因為交易失敗，所以項目在資料表中保持不變。電影`actingYear`的值`Sophie's Choice`為 `1982`，如呼叫 `transactWriteItem()` 方法之前資料表中項目的第 2 行所示。

若要擷取交易的取消資訊，請將`transactWriteItems()`方法呼叫括在 `try` 區塊和 `catch` 中[https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/TransactionCanceledException.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/TransactionCanceledException.html)。在範例的註解行 4 之後，程式碼會記錄每個`[CancellationReason](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/CancellationReason.html)`物件。由於範例註解行 3 後面的程式碼指定應針對導致交易失敗的項目傳回值，因此日誌會顯示`Sophie's Choice`電影項目的原始資料庫值。

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

### 單一操作條件範例
<a name="ddb-en-client-use-multiop-trans-writeitems-opcondition"></a>

下列範例顯示對交易請求中的單一操作使用條件。註解行 1 後的刪除操作包含一個條件，可針對資料庫檢查操作目標項目的值。在此範例中，註解行 2 之後使用協助程式方法建立的條件表達式指定，如果電影的動作年份不等於 2013 年，則應從資料庫中刪除項目。

本指南稍後會討論[表達式](ddb-en-client-expressions.md)。

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

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

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

下列協助程式方法用於先前的程式碼範例。

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

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

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

DynamoDB 資料表在程式碼範例執行之前包含下列項目。

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

程式碼完成執行後，資料表中會包含下列項目。

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

因為交易失敗，所以項目在資料表中保持不變。電影`actingYear`的值`Blue Jasmine``2013`如程式碼範例執行前項目清單中的第 2 行所示。

以下幾行會記錄到 主控台。

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

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

次要索引透過定義您在查詢和掃描操作中使用的替代索引鍵來改善資料存取。全域次要索引 (GSI) 具有分割區索引鍵和排序索引鍵，可與基礎資料表上的索引鍵不同。相反地，本機次要索引 (LSI) 會使用主要索引的分割區索引鍵。

## 使用次要索引註釋標註資料類別
<a name="ddb-en-client-use-secindex-annomodel"></a>

參與次要索引的屬性需要 `@DynamoDbSecondaryPartitionKey`或 `@DynamoDbSecondarySortKey`註釋。

以下類別顯示兩個索引的註釋。名為 *SubjectLastPostedDateIndex* 的 GSI 會使用分割區索引鍵的 屬性，以及排序索引鍵`LastPostedDateTime`的 `Subject` 屬性。名為 *ForumLastPostedDateIndex* 的 LSI 會使用 `ForumName`做為分割區索引鍵，並使用 `LastPostedDateTime`做為排序索引鍵。

請注意， `Subject` 屬性提供雙重角色。這是主索引鍵的排序索引鍵，以及名為 *SubjectLastPostedDateIndex* 之 GSI 的分割區索引鍵。

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

`MessageThread` 類別適用於 *Amazon DynamoDB 開發人員指南*中[範例 Thread 資料表](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/AppendixSampleTables.html)的資料類別。

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

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

import java.util.List;
```

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

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

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

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

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

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

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

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

    public String getLastPostedBy() {
        return LastPostedBy;
    }

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

    public Integer getViews() {
        return Views;
    }

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

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

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

    public Integer getAnswered() {
        return Answered;
    }

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

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

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

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

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

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

從適用於 Java 的 SDK 第 2.20.86 版開始， `createTable()`方法會自動從資料類別註釋產生次要索引。根據預設，基礎資料表中的所有屬性都會複製到 索引，而佈建的輸送量值為 20 個讀取容量單位和 20 個寫入容量單位。

不過，如果您使用 2.20.86 之前的 SDK 版本，則需要建置索引與 資料表，如下列範例所示。此範例會建置`Thread`資料表的兩個索引。[建置器](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/CreateTableEnhancedRequest.Builder.html)參數具有設定這兩種索引類型的方法，如註解行 1 和 2 所示。您可以使用索引建置器的 `indexName()`方法，將資料類別註釋中指定的索引名稱與預期的索引類型建立關聯。

此程式碼會將所有資料表屬性設定為在註解行 3 和 4 之後的兩個索引中結束。如需[屬性投影](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/LSI.html#LSI.Projections)的詳細資訊，請參閱 *Amazon DynamoDB 開發人員指南*。

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

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

下列範例會查詢本機次要索引 *ForumLastPostedDateIndex*。

在註解行 2 之後，您可以建立呼叫 [DynamoDbIndex.query()](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbIndex.html#query(java.util.function.Consumer)) 方法時所需的 [QueryConditional](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/QueryConditional.html) 物件。

透過傳入索引名稱，您可以在註解行 3 之後取得要查詢的索引參考。在註解行 4 之後，您可以在傳入`QueryConditional`物件的索引上呼叫 `query()`方法。

您也可以將查詢設定為傳回三個屬性值，如註解行 5 所示。如果`attributesToProject()`未呼叫 ，查詢會傳回所有屬性值。請注意，指定的屬性名稱以小寫字母開頭。這些屬性名稱符合資料表中使用的屬性名稱，不一定是資料類別的屬性名稱。

在註解行 6 之後，逐一查看結果並記錄查詢傳回的每個項目，並將它存放在清單中以傳回給發起人。

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

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

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

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

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

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

在執行查詢之前，資料庫中存在下列項目。

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

第 1 行和第 6 行的記錄陳述式會產生下列主控台輸出。

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

查詢傳回`forumName`的值為 *Forum02* 且`lastPostedDateTime`值大於或等於 *2023.03.31* 的項目。結果會顯示具有空字串`message`的值，但`message`屬性在索引中具有值。這是因為註解行 5 之後的程式碼未投影訊息屬性。

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

了解 DynamoDB 增強型用戶端 API 中的進階資料表結構描述功能。

## 了解資料表結構描述類型
<a name="ddb-en-client-adv-features-schm-overview"></a>

`[TableSchema](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/TableSchema.html)` 是 DynamoDB 增強型用戶端 API 映射功能的介面。它可以在 [AttributeValues](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/services/dynamodb/model/AttributeValue.html) 的映射中映射資料物件。`TableSchema` 物件需要知道要映射之資料表的結構。此結構資訊會存放在 [https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/TableMetadata.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/TableMetadata.html) 物件中。

增強型用戶端 API 有數個 實作`TableSchema`，如下。

### 從註釋類別產生的資料表結構描述
<a name="ddb-en-client-adv-features-schema-mapped"></a>

從`TableSchema`註釋類別建置 是一項中等昂貴的操作，因此我們建議在應用程式啟動時執行此操作一次。

 [ BeanTableSchema ](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/mapper/BeanTableSchema.html)   
此實作是根據 Bean 類別的屬性和註釋建置。此方法的範例會在[入門區段](ddb-en-client-gs-tableschema.md#ddb-en-client-gs-tableschema-anno-bean)中示範。  
如果 `BeanTableSchema` 的行為不如預期，請啟用 的偵錯記錄`software.amazon.awssdk.enhanced.dynamodb.beans`。

[ImmutableTableSchema](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/mapper/ImmutableTableSchema.html)  
此實作是從不可變的資料類別建置而成。此方法說明於 [使用不可變的資料類別](ddb-en-client-use-immut.md)一節。

### 建置器產生的資料表結構描述
<a name="ddb-en-client-adv-features-schema-static"></a>

下列 `TableSchema`是使用建置器從程式碼建置。此方法的成本低於使用註釋資料類別的方法。建置器方法可避免使用註釋，且不需要 JavaBean 命名標準。

[StaticTableSchema](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/mapper/StaticTableSchema.html)  
此實作專為可變資料類別而建置。本指南的入門章節示範如何使用[`StaticTableSchema`建置器產生](ddb-en-client-gs-tableschema.md#ddb-en-client-gs-tableschema-builder) 。

[StaticImmutableTableSchema](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/mapper/StaticImmutableTableSchema.html)  
與您建置 的方式類似`StaticTableSchema`，您可以使用`TableSchema`[建置器](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/mapper/StaticImmutableTableSchema.html)來產生此類型 的實作，以搭配不可變的資料類別使用。

### 無固定結構描述之資料的資料表結構描述
<a name="ddb-en-client-adv-features-schema-document"></a>

[DocumentTableSchema](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/document/DocumentTableSchema.html)  
與其他 實作不同`TableSchema`，您不定義`DocumentTableSchema`執行個體的屬性。通常，您只能指定主索引鍵和屬性轉換器提供者。`EnhancedDocument` 執行個體提供您從個別元素或 JSON 字串建置的屬性。

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

DynamoDB 增強型用戶端 API 提供註釋，以排除資料類別屬性成為資料表上的屬性。透過 API，您也可以使用與資料類別屬性名稱不同的屬性名稱。

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

若要忽略不應映射至 DynamoDB 資料表的屬性，請使用 `@DynamoDbIgnore`註釋標記屬性。

```
private String internalKey;

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

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

若要變更 DynamoDB 資料表中使用的屬性名稱，請以`@DynamoDbAttribute`註釋標記它，並提供不同的名稱。

```
private String internalKey;

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

# 控制屬性轉換
<a name="ddb-en-client-adv-features-conversion"></a>

根據預設，資料表結構描述會透過 `[AttributeConverterProvider](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/AttributeConverterProvider.html)` 介面的預設實作，為許多常見的 Java 類型提供轉換器。您可以使用自訂 `AttributeConverterProvider` 實作變更整體預設行為。您也可以變更單一屬性的轉換器。

如需可用轉換器的清單，請參閱 [AttributeConverter](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/AttributeConverter.html) 介面 Java 文件。

## 提供自訂屬性轉換器提供者
<a name="ddb-en-client-adv-features-conversion-prov"></a>

您可以透過 `AttributeConverterProvider``@DynamoDbBean``(converterProviders = {…})`註釋提供單一`AttributeConverterProvider`或一組排序的 。任何自訂`AttributeConverterProvider`都必須擴展`AttributeConverterProvider`界面。

請注意，如果您提供自己的屬性轉換器提供者鏈，您將覆寫預設轉換器提供者 `DefaultAttributeConverterProvider`。如果您想要使用 的功能`DefaultAttributeConverterProvider`，則必須將其包含在鏈中。

您也可以使用空陣列 註釋 Bean`{}`。這會停用任何屬性轉換器提供者的使用，包括預設值。在這種情況下，所有要映射的屬性都必須有自己的屬性轉換器。

下列程式碼片段顯示單一轉換器供應商。

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

}
```

下列程式碼片段顯示轉換器供應商鏈的使用方式。由於 SDK 預設為最後提供，因此具有最低的優先順序。

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

}
```

靜態資料表結構描述建置器具有以相同方式運作`attributeConverterProviders()`的方法。這會顯示在下列程式碼片段中。

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

## 覆寫單一屬性的映射
<a name="ddb-en-client-adv-features-conversion-single"></a>

若要覆寫單一屬性的映射方式，請提供 屬性`AttributeConverter`的 。此新增會覆寫資料表結構描述`AttributeConverterProviders`中 提供的任何轉換器。這只會為該屬性新增自訂轉換器。除非針對其他屬性明確指定，否則其他屬性，即使是相同類型的屬性也不會使用該轉換器。

`@DynamoDbConvertedBy` 註釋用於指定自訂`AttributeConverter`類別，如下列程式碼片段所示。

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

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

靜態結構描述的建置器具有同等屬性建置器`attributeConverter()`方法。此方法採用 的執行個體`AttributeConverter`，如下所示。

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

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

此範例顯示為[https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/HttpCookie.html](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/net/HttpCookie.html)物件提供屬性轉換器的`AttributeConverterProvider`實作。

下列`SimpleUser`類別包含名為 的屬性`lastUsedCookie`，該屬性是 的執行個體`HttpCookie`。

`@DynamoDbBean` 註釋的 參數會列出提供轉換器的兩個`AttributeConverterProvider`類別。

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

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

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

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

        public HttpCookie getLastUsedCookie() {
            return lastUsedCookie;
        }

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

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

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

------

下列範例中`CookieConverterProvider`的 提供 的執行個體`HttpCookeConverter`。

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

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

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

### 轉換程式碼
<a name="ddb-en-client-adv-features-conversion-example-code"></a>

在下列`HttpCookieConverter`類別的 `transformFrom()`方法中，程式碼會接收`HttpCookie`執行個體，並將其轉換為存放為 屬性的 DynamoDB 映射。

`transformTo()` 方法會接收 DynamoDB `HttpCookie` 映射參數，然後調用需要名稱和值的建構函數。

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

        @Override
        public AttributeValue transformFrom(HttpCookie httpCookie) {

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

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

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

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

# 變更屬性的更新行為
<a name="ddb-en-client-adv-features-upd-behavior"></a>

您可以在執行更新操作時自訂個別屬性的*更新*行為。DynamoDB 增強型用戶端 API 中的一些更新操作範例是 [updateItem()](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbTable.html#updateItem(T)) 和 [transactWriteItems()](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedClient.html#transactWriteItems(java.util.function.Consumer))。

例如，假設您想要在記錄上存放*時間戳記上建立*的 。不過，只有在資料庫中沒有屬性的現有值時，才希望寫入其值。在此情況下，您會使用`[WRITE\$1IF\$1NOT\$1EXISTS](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/mapper/UpdateBehavior.html#WRITE_IF_NOT_EXISTS)`更新行為。

下列範例顯示將行為新增至 `createdOn` 屬性的註釋。

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

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

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

您可以在建置靜態資料表結構描述時宣告相同的更新行為，如註解行 1 後的下列範例所示。

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

# 扁平化其他類別的屬性
<a name="ddb-en-client-adv-features-flatmap"></a>

如果資料表的屬性分散在數個不同的 Java 類別，無論是透過繼承或合成，DynamoDB 增強型用戶端 API 都支援將屬性扁平化為一個類別。

## 使用繼承
<a name="ddb-en-client-adv-features-flatmap-inheritance"></a>

如果您的類別使用繼承，請使用下列方法來扁平化階層。

### 使用註釋的 Bean
<a name="ddb-en-client-adv-features-flatmap-inheritance-anno"></a>

對於註釋方法，兩個類別都必須帶有`@DynamoDbBean`註釋，而類別必須帶有一個或多個主索引鍵註釋。

以下顯示具有繼承關係的資料類別範例。

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

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

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

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

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

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

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

Lombok [`onMethod`的選項](https://projectlombok.org/features/experimental/onX)會將屬性型 DynamoDB 註釋，例如 `@DynamoDbPartitionKey`，複製到產生的程式碼。

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

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

------

### 使用靜態結構描述
<a name="ddb-en-client-adv-features-flatmap-inheritance-static"></a>

對於靜態結構描述方法，請使用建置器的 `extend()`方法，將父類別的屬性摺疊至子類別。這會顯示在下列範例中的註解行 1 之後。

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

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

先前的靜態結構描述範例使用下列資料類別。由於映射是在您建置靜態資料表結構描述時定義的，因此資料類別不需要註釋。

#### 資料類別
<a name="gunk"></a>

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

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

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


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

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

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

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

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

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

------

## 使用合成
<a name="ddb-en-client-adv-features-flatmap-comp"></a>

如果您的類別使用合成，請使用下列方法來扁平化階層。

### 使用註釋的 Bean
<a name="ddb-en-client-adv-features-flatmap-comp-anno"></a>

`@DynamoDbFlatten` 註釋會扁平化包含的類別。

下列資料類別範例使用`@DynamoDbFlatten`註釋，有效地將包含`GenericRecord`類別的所有屬性新增至`Customer`類別。

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

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

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

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

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

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

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

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

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

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

------

您可以使用平面註釋，視需要平面化任意數量的不同合格類別。以下為目前的限制：
+ 所有屬性名稱在平面化後必須是唯一的。
+ 不得有一個以上的分割區索引鍵、排序索引鍵或資料表名稱。

### 使用靜態結構描述
<a name="ddb-en-client-adv-features-flatmap-comp-static"></a>

當您建置靜態資料表結構描述時，請使用建置器的 `flatten()`方法。您也可以提供識別包含類別的 getter 和 setter 方法。

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

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

先前的靜態結構描述範例使用下列資料類別。

#### 資料類別
<a name="ddb-en-client-adv-features-flatmap-comp-static-supporting"></a>

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

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

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

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

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

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

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

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

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

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

------

您可以使用建置器模式，視需要水平化任意數量的不同合格類別。

## 對其他程式碼的影響
<a name="ddb-en-client-adv-features-flatmap-compare"></a>

當您使用 `@DynamoDbFlatten` 屬性 （或`flatten()`建置器方法） 時，DynamoDB 中的項目會包含所撰寫物件的每個屬性的屬性。它還包含編寫物件的屬性。

相反地，如果您使用編寫的類別註釋資料類別，但不使用 `@DynamoDbFlatten`，則項目會與編寫的物件一起儲存為單一屬性。

例如，比較平面化中顯示的`Customer`類別與合成範例，包含和不包含`record`屬性的平面化。 [使用註釋的 Bean](#ddb-en-client-adv-features-flatmap-comp-anno)您可以使用 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、地圖、清單和集合等屬性
<a name="ddb-en-client-adv-features-nested"></a>

Bean 定義，例如如下所示的`Person`類別，可能會定義參考具有其他屬性之類型的屬性 （或屬性）。例如，在 `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 映射、清單或 Bean 做為*文件類型*。您在 Java 中用於其資料值的簡單屬性在 DynamoDB 中稱為*純量類型*。集合，其中包含相同類型的多個純量元素，稱為*集合類型*。

請務必了解，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 增強型用戶端 API 使用以下程式碼片段建置`Person`類別的資料表結構描述時，API 會探索 `Address`和 `PhoneNumber`類別的使用，並建置對應的映射以搭配 DynamoDB 使用。

```
TableSchema<Person> personTableSchema = TableSchema.fromBean(Person.class);
```

### 搭配建置器使用抽象結構描述
<a name="ddb-en-client-adv-features-nested-map-builder"></a>

替代方法是針對每個巢狀 Bean 類別使用靜態資料表結構描述建置器，如下列程式碼所示。

`Address` 和 `PhoneNumber`類別的資料表結構描述抽象，因為它們無法與 DynamoDB 資料表搭配使用。這是因為它們缺少主索引鍵的定義。不過，它們會在 `Person`類別的資料表結構描述中用作巢狀結構描述。

在 定義的註解行 1 和 2 之後`PERSON_TABLE_SCHEMA`，您會看到使用抽象資料表結構描述的程式碼。在 `EnhanceType.documentOf(...)` 方法`documentOf`中使用 並不表示 方法傳回 `EnhancedDocument`類型的增強型文件 API。此內容中的 `documentOf(...)`方法會傳回 物件，該物件知道如何使用資料表結構描述引數將其類別引數對應至 DynamoDB 資料表屬性。

#### 靜態結構描述程式碼
<a name="ddb-en-client-adv-features-nested-map-builder-code"></a>

```
    // Abstract table schema that cannot be used to work with a DynamoDB table,
    // but can be used as a nested schema.
    public static final TableSchema<Address> TABLE_SCHEMA_ADDRESS = TableSchema.builder(Address.class)
        .newItemSupplier(Address::new)
        .addAttribute(String.class, a -> a.name("street")
            .getter(Address::getStreet)
            .setter(Address::setStreet))
        .addAttribute(String.class, a -> a.name("city")
            .getter(Address::getCity)
            .setter(Address::setCity))
        .addAttribute(String.class, a -> a.name("zipcode")
            .getter(Address::getZipCode)
            .setter(Address::setZipCode))
        .addAttribute(String.class, a -> a.name("state")
            .getter(Address::getState)
            .setter(Address::setState))
        .build();

    // Abstract table schema that cannot be used to work with a DynamoDB table,
    // but can be used as a nested schema.
    public static final TableSchema<PhoneNumber> TABLE_SCHEMA_PHONENUMBER = TableSchema.builder(PhoneNumber.class)
        .newItemSupplier(PhoneNumber::new)
        .addAttribute(String.class, a -> a.name("type")
            .getter(PhoneNumber::getType)
            .setter(PhoneNumber::setType))
        .addAttribute(String.class, a -> a.name("number")
            .getter(PhoneNumber::getNumber)
            .setter(PhoneNumber::setNumber))
        .build();

    // A static table schema that can be used with a DynamoDB table.
    // The table schema contains two nested schemas that are used to perform mapping to/from DynamoDB.
    public static final TableSchema<Person> PERSON_TABLE_SCHEMA =
        TableSchema.builder(Person.class)
            .newItemSupplier(Person::new)
            .addAttribute(Integer.class, a -> a.name("id")
                .getter(Person::getId)
                .setter(Person::setId)
                .addTag(StaticAttributeTags.primaryPartitionKey()))
            .addAttribute(String.class, a -> a.name("firstName")
                .getter(Person::getFirstName)
                .setter(Person::setFirstName))
            .addAttribute(String.class, a -> a.name("lastName")
                .getter(Person::getLastName)
                .setter(Person::setLastName))
            .addAttribute(Integer.class, a -> a.name("age")
                .getter(Person::getAge)
                .setter(Person::setAge))
            .addAttribute(EnhancedType.documentOf(Address.class, TABLE_SCHEMA_ADDRESS), a -> a.name("mainAddress")
                .getter(Person::getMainAddress)
                .setter(Person::setMainAddress))
            .addAttribute(EnhancedType.listOf(String.class), a -> a.name("hobbies")
                .getter(Person::getHobbies)
                .setter(Person::setHobbies))
            .addAttribute(EnhancedType.mapOf(
                EnhancedType.of(String.class),
                // 1. Use mapping functionality of the Address table schema.
                EnhancedType.documentOf(Address.class, TABLE_SCHEMA_ADDRESS)), a -> a.name("addresses")
                .getter(Person::getAddresses)
                .setter(Person::setAddresses))
            .addAttribute(EnhancedType.listOf(
                // 2. Use mapping functionality of the PhoneNumber table schema.
                EnhancedType.documentOf(PhoneNumber.class, TABLE_SCHEMA_PHONENUMBER)), a -> a.name("phoneNumbers")
                .getter(Person::getPhoneNumbers)
                .setter(Person::setPhoneNumbers))
            .build();
```

## 複雜類型的專案屬性
<a name="ddb-en-client-adv-features-nested-projection"></a>

對於 `query()`和 `scan()`方法，您可以使用 `addNestedAttributeToProject()`和 等方法呼叫，指定要在結果中傳回的屬性`attributesToProject()`。DynamoDB 增強型用戶端 API 在傳送請求之前，會將 Java 方法呼叫參數轉換為[投影表達式](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.ProjectionExpressions.html)。

下列範例會將兩個項目填入`Person`資料表，然後執行三個掃描操作。

第一次掃描會存取資料表中的所有項目，以便將結果與其他掃描操作進行比較。

第二次掃描使用[https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/ScanEnhancedRequest.Builder.html#addNestedAttributeToProject(software.amazon.awssdk.enhanced.dynamodb.NestedAttributeName)](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/ScanEnhancedRequest.Builder.html#addNestedAttributeToProject(software.amazon.awssdk.enhanced.dynamodb.NestedAttributeName))建置器方法來僅傳回`street`屬性值。

第三個掃描操作使用[https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/ScanEnhancedRequest.Builder.html#attributesToProject(java.lang.String...)](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/model/ScanEnhancedRequest.Builder.html#attributesToProject(java.lang.String...))建置器方法來傳回第一層屬性 的資料`hobbies`。的屬性類型`hobbies`是清單。若要存取個別清單項目，請在清單上執行 `get()`操作。

```
        personDynamoDbTable = getDynamoDbEnhancedClient().table("Person", PERSON_TABLE_SCHEMA);
        PersonUtils.createPersonTable(personDynamoDbTable, getDynamoDbClient());
        // Use a utility class to add items to the Person table.
        List<Person> personList = PersonUtils.getItemsForCount(2);
        // This utility method performs a put against DynamoDB to save the instances in the list argument.
        PersonUtils.putCollection(getDynamoDbEnhancedClient(), personList, personDynamoDbTable);

        // The first scan logs all items in the table to compare to the results of the subsequent scans.
        final PageIterable<Person> allItems = personDynamoDbTable.scan();
        allItems.items().forEach(p ->
                // 1. Log what is in the table.
                logger.info(p.toString()));

        // Scan for nested attributes.
        PageIterable<Person> streetScanResult = personDynamoDbTable.scan(b -> b
                // Use the 'addNestedAttributeToProject()' or 'addNestedAttributesToProject()' to access data nested in maps in DynamoDB.
                .addNestedAttributeToProject(
                        NestedAttributeName.create("addresses", "work", "street")
                ));

        streetScanResult.items().forEach(p ->
                //2. Log the results of requesting nested attributes.
                logger.info(p.toString()));

        // Scan for a top-level list attribute.
        PageIterable<Person> hobbiesScanResult = personDynamoDbTable.scan(b -> b
                // Use the 'attributesToProject()' method to access first-level attributes.
                .attributesToProject("hobbies"));

        hobbiesScanResult.items().forEach((p) -> {
            // 3. Log the results of the request for the 'hobbies' attribute.
            logger.info(p.toString());
            // To access an item in a list, first get the parent attribute, 'hobbies', then access items in the list.
            String hobby = p.getHobbies().get(1);
            // 4. Log an item in the list.
            logger.info(hobby);
        });
```

```
// Logged results from comment line 1.
Person{id=2, firstName='first name 2', lastName='last name 2', age=11, addresses={work=Address{street='street 21', city='city 21', state='state 21', zipCode='33333'}, home=Address{street='street 2', city='city 2', state='state 2', zipCode='22222'}}, phoneNumbers=[PhoneNumber{type='home', number='222-222-2222'}, PhoneNumber{type='work', number='333-333-3333'}], hobbies=[hobby 2, hobby 21]}
Person{id=1, firstName='first name 1', lastName='last name 1', age=11, addresses={work=Address{street='street 11', city='city 11', state='state 11', zipCode='22222'}, home=Address{street='street 1', city='city 1', state='state 1', zipCode='11111'}}, phoneNumbers=[PhoneNumber{type='home', number='111-111-1111'}, PhoneNumber{type='work', number='222-222-2222'}], hobbies=[hobby 1, hobby 11]}

// Logged results from comment line 2.
Person{id=null, firstName='null', lastName='null', age=null, addresses={work=Address{street='street 21', city='null', state='null', zipCode='null'}}, phoneNumbers=null, hobbies=null}
Person{id=null, firstName='null', lastName='null', age=null, addresses={work=Address{street='street 11', city='null', state='null', zipCode='null'}}, phoneNumbers=null, hobbies=null}

// Logged results from comment lines 3 and 4.
Person{id=null, firstName='null', lastName='null', age=null, addresses=null, phoneNumbers=null, hobbies=[hobby 2, hobby 21]}
hobby 21
Person{id=null, firstName='null', lastName='null', age=null, addresses=null, phoneNumbers=null, hobbies=[hobby 1, hobby 11]}
hobby 11
```

**注意**  
如果 `attributesToProject()`方法遵循任何其他新增您要投影之屬性的建置器方法，則提供給 的屬性名稱清單會`attributesToProject()`取代所有其他屬性名稱。  
在下列程式碼片段中使用`ScanEnhancedRequest`執行個體執行的掃描，只會傳回愛好資料。  

```
ScanEnhancedRequest lastOverwrites = ScanEnhancedRequest.builder()
        .addNestedAttributeToProject(
                NestedAttributeName.create("addresses", "work", "street"))
        .addAttributeToProject("firstName")
        // If the 'attributesToProject()' method follows other builder methods that add attributes for projection,
        // its list of attributes replace all previous attributes.
        .attributesToProject("hobbies")
        .build();
PageIterable<Person> hobbiesOnlyResult = personDynamoDbTable.scan(lastOverwrites);
hobbiesOnlyResult.items().forEach(p ->
        logger.info(p.toString()));

// Logged results.
Person{id=null, firstName='null', lastName='null', age=null, addresses=null, phoneNumbers=null, hobbies=[hobby 2, hobby 21]}
Person{id=null, firstName='null', lastName='null', age=null, addresses=null, phoneNumbers=null, hobbies=[hobby 1, hobby 11]}
```
下列程式碼片段會先使用 `attributesToProject()`方法。此排序會保留所有其他請求的屬性。  

```
ScanEnhancedRequest attributesPreserved = ScanEnhancedRequest.builder()
        // Use 'attributesToProject()' first so that the method call does not replace all other attributes
        // that you want to project.
        .attributesToProject("firstName")
        .addNestedAttributeToProject(
                NestedAttributeName.create("addresses", "work", "street"))
        .addAttributeToProject("hobbies")
        .build();
PageIterable<Person> allAttributesResult = personDynamoDbTable.scan(attributesPreserved);
allAttributesResult.items().forEach(p ->
        logger.info(p.toString()));

// Logged results.
Person{id=null, firstName='first name 2', lastName='null', age=null, addresses={work=Address{street='street 21', city='null', state='null', zipCode='null'}}, phoneNumbers=null, hobbies=[hobby 2, hobby 21]}
Person{id=null, firstName='first name 1', lastName='null', age=null, addresses={work=Address{street='street 11', city='null', state='null', zipCode='null'}}, phoneNumbers=null, hobbies=[hobby 1, hobby 11]}
```

## 在表達式中使用複雜類型
<a name="ddb-en-client-adv-features-nested-expressions"></a>

您可以在表達式中使用複雜類型，例如篩選條件表達式和條件表達式，方法是使用取消參考運算子來導覽複雜類型的結構。對於物件和映射，請使用 `. (dot)`和 來列出元素 `[n]`（元素序號周圍的方括號）。您無法參考集合的個別元素，但您可以使用 [`contains`函數](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.OperatorsAndFunctions.html#Expressions.OperatorsAndFunctions.Functions)。

下列範例顯示掃描操作中使用的兩個篩選條件表達式。篩選條件表達式會指定您希望結果中項目的比對條件。此範例使用先前顯示的 `Address`、 `Person`和 `PhoneNumber`類別。

```
    public void scanUsingFilterOfNestedAttr() {
        // The following is a filter expression for an attribute that is a map of Address objects.
        // By using this filter expression, the SDK returns Person objects that have an address
        // with 'mailing' as a key and 'MS2' for a state value.
        Expression addressFilter = Expression.builder()
                .expression("addresses.#type.#field = :value")
                .putExpressionName("#type", "mailing")
                .putExpressionName("#field", "state")
                .putExpressionValue(":value", AttributeValue.builder().s("MS2").build())
                .build();

        PageIterable<Person> addressFilterResults = personDynamoDbTable.scan(rb -> rb.
                filterExpression(addressFilter));
        addressFilterResults.items().stream().forEach(p -> logger.info("Person: {}", p));

        assert addressFilterResults.items().stream().count() == 1;


        // The following is a filter expression for an attribute that is a list of phone numbers.
        // By using this filter expression, the SDK returns Person objects whose second phone number
        // in the list has a type equal to 'cell'.
        Expression phoneFilter = Expression.builder()
                .expression("phoneNumbers[1].#type = :type")
                .putExpressionName("#type", "type")
                .putExpressionValue(":type", AttributeValue.builder().s("cell").build())
                .build();

        PageIterable<Person> phoneFilterResults = personDynamoDbTable.scan(rb -> rb
                .filterExpression(phoneFilter)
                .attributesToProject("id", "firstName", "lastName", "phoneNumbers")
        );

        phoneFilterResults.items().stream().forEach(p -> logger.info("Person: {}", p));

        assert phoneFilterResults.items().stream().count() == 1;
        assert phoneFilterResults.items().stream().findFirst().get().getPhoneNumbers().get(1).getType().equals("cell");
    }
```

### 填入資料表的協助程式方法
<a name="nested-expressions-helper-method"></a>

```
    public static void populateDatabase() {
        Person person1 = new Person();
        person1.setId(1);
        person1.setFirstName("FirstName1");
        person1.setLastName("LastName1");

        Address billingAddr1 = new Address();
        billingAddr1.setState("BS1");
        billingAddr1.setCity("BillingTown1");

        Address mailing1 = new Address();
        mailing1.setState("MS1");
        mailing1.setCity("MailingTown1");

        person1.setAddresses(Map.of("billing", billingAddr1, "mailing", mailing1));

        PhoneNumber pn1_1 = new PhoneNumber();
        pn1_1.setType("work");
        pn1_1.setNumber("111-111-1111");

        PhoneNumber pn1_2 = new PhoneNumber();
        pn1_2.setType("home");
        pn1_2.setNumber("222-222-2222");

        List<PhoneNumber> phoneNumbers1 = List.of(pn1_1, pn1_2);
        person1.setPhoneNumbers(phoneNumbers1);

        personDynamoDbTable.putItem(person1);

        Person person2 = person1;
        person2.setId(2);
        person2.setFirstName("FirstName2");
        person2.setLastName("LastName2");

        Address billingAddress2 = billingAddr1;
        billingAddress2.setCity("BillingTown2");
        billingAddress2.setState("BS2");

        Address mailing2 = mailing1;
        mailing2.setCity("MailingTown2");
        mailing2.setState("MS2");

        person2.setAddresses(Map.of("billing", billingAddress2, "mailing", mailing2));

        PhoneNumber pn2_1 = new PhoneNumber();
        pn2_1.setType("work");
        pn2_1.setNumber("333-333-3333");

        PhoneNumber pn2_2 = new PhoneNumber();
        pn2_2.setType("cell");
        pn2_2.setNumber("444-444-4444");

        List<PhoneNumber> phoneNumbers2 = List.of(pn2_1, pn2_2);
        person2.setPhoneNumbers(phoneNumbers2);

        personDynamoDbTable.putItem(person2);
    }
```

### 資料庫中項目的 JSON 表示法
<a name="nested-attributes-expression-json-items"></a>

```
{
 "id": 1,
 "addresses": {
  "billing": {
   "city": "BillingTown1",
   "state": "BS1",
   "street": null,
   "zipCode": null
  },
  "mailing": {
   "city": "MailingTown1",
   "state": "MS1",
   "street": null,
   "zipCode": null
  }
 },
 "firstName": "FirstName1",
 "lastName": "LastName1",
 "phoneNumbers": [
  {
   "number": "111-111-1111",
   "type": "work"
  },
  {
   "number": "222-222-2222",
   "type": "home"
  }
 ]
}

{
 "id": 2,
 "addresses": {
  "billing": {
   "city": "BillingTown2",
   "state": "BS2",
   "street": null,
   "zipCode": null
  },
  "mailing": {
   "city": "MailingTown2",
   "state": "MS2",
   "street": null,
   "zipCode": null
  }
 },
 "firstName": "FirstName2",
 "lastName": "LastName2",
 "phoneNumbers": [
  {
   "number": "333-333-3333",
   "type": "work"
  },
  {
   "number": "444-444-4444",
   "type": "cell"
  }
 ]
}
```

## 更新包含複雜類型的項目
<a name="ddb-en-client-adv-features-nested-updates"></a>

若要更新包含複雜類型的項目，您有兩種基本方法：
+ 方法 1：先擷取項目 （使用 `getItem`)、更新物件，然後呼叫 `DynamoDbTable#updateItem`。
+ 方法 2：不要擷取項目，但建構新的執行個體、設定您要更新的屬性，並透過`DynamoDbTable#updateItem`設定適當的 值將執行個體提交至 [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) 。此方法不需要您在更新項目之前擷取該項目。

本節中顯示的範例使用先前顯示的 `Address`、 `Person`和 `PhoneNumber`類別。

### 更新方法 1：擷取，然後更新
<a name="ddb-en-client-adv-features-nested-updates-retreive"></a>

透過使用此方法，您可以確保更新時不會遺失任何資料。DynamoDB 增強型用戶端 API 會使用儲存在 DynamoDB 中的項目的屬性重新建立 Bean，包括複雜類型的值。然後，您需要使用 getter 和 setter 來更新 bean。此方法的缺點是您先擷取項目所產生的成本。

下列範例示範，如果您在更新項目之前先擷取項目，則不會遺失任何資料。

```
    public void retrieveThenUpdateExample()  {
        // Assume that we ran this code yesterday.
        Person person = new Person();
        person.setId(1);
        person.setFirstName("FirstName");
        person.setLastName("LastName");

        Address mainAddress = new Address();
        mainAddress.setStreet("123 MyStreet");
        mainAddress.setCity("MyCity");
        mainAddress.setState("MyState");
        mainAddress.setZipCode("MyZipCode");
        person.setMainAddress(mainAddress);

        PhoneNumber homePhone = new PhoneNumber();
        homePhone.setNumber("1111111");
        homePhone.setType("HOME");
        person.setPhoneNumbers(List.of(homePhone));

        personDynamoDbTable.putItem(person);

        // Assume that we are running this code now.
        // First, retrieve the item
        Person retrievedPerson = personDynamoDbTable.getItem(Key.builder().partitionValue(1).build());

        // Make any updates.
        retrievedPerson.getMainAddress().setCity("YourCity");

        // Save the updated bean. 'updateItem' returns the bean as it appears after the update.
        Person updatedPerson = personDynamoDbTable.updateItem(retrievedPerson);

        // Verify for this example.
        Address updatedMainAddress = updatedPerson.getMainAddress();
        assert updatedMainAddress.getCity().equals("YourCity");
        assert updatedMainAddress.getState().equals("MyState"); // Unchanged.
        // The list of phone numbers remains; it was not set to null;
        assert updatedPerson.getPhoneNumbers().size() == 1;
    }
```

### 更新方法 2：使用`IgnoreNullsMode`列舉而不先擷取項目
<a name="ddb-en-client-adv-features-nested-updates-nullmode"></a>

若要更新 DynamoDB 中的項目，您可以提供只有要更新屬性的新物件，並將其他值保留為 null。使用此方法時，您需要了解 SDK 如何處理物件中的 null 值，以及如何控制行為。

若要指定您希望 SDK 忽略哪些 null 值屬性，請在建置 時提供`IgnoreNullsMode`列舉[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.SCALAR_ONLY` 模式。

```
// Create a new Person object to update the existing item in DynamoDB.
Person personForUpdate = new Person();
personForUpdate.setId(1);
personForUpdate.setFirstName("updatedFirstName");  // 'firstName' is a top scalar property.

Address addressForUpdate = new Address();
addressForUpdate.setCity("updatedCity");
personForUpdate.setMainAddress(addressForUpdate);

personDynamoDbTable.updateItem(r -> r
                .item(personForUpdate)
                .ignoreNullsMode(IgnoreNullsMode.SCALAR_ONLY));

/* With IgnoreNullsMode.SCALAR_ONLY provided, The SDK ignores all null properties. The SDK adds or replaces
the 'firstName' property with the provided value, "updatedFirstName". The SDK updates the 'city' value of
'mainAddress', as long as the 'mainAddress' attribute already exists in DynamoDB.

In the background, the SDK generates an update expression that it sends in the request to DynamoDB.
The following JSON object is a simplified version of what it sends. Notice that the SDK includes the paths
to 'mainAddress.city' and 'firstName' in the SET clause of the update expression. No null values in
'personForUpdate' are included.

{
  "TableName": "PersonTable",
  "Key": {
    "id": {
      "N": "1"
    }
  },
  "ReturnValues": "ALL_NEW",
  "UpdateExpression": "SET #mainAddress.#city = :mainAddress_city, #firstName = :firstName",
  "ExpressionAttributeNames": {
    "#city": "city",
    "#firstName": "firstName",
    "#mainAddress": "mainAddress"
  },
  "ExpressionAttributeValues": {
    ":firstName": {
      "S": "updatedFirstName"
    },
    ":mainAddress_city": {
      "S": "updatedCity"
    }
  }
}

Had we chosen 'IgnoreNullsMode.DEFAULT' instead of 'IgnoreNullsMode.SCALAR_ONLY', the SDK would have included
null values in the "ExpressionAttributeValues" section of the request as shown in the following snippet.

  "ExpressionAttributeValues": {
    ":mainAddress": {
      "M": {
        "zipCode": {
          "NULL": true
        },
        "city": {
          "S": "updatedCity"
        },
        "street": {
          "NULL": true
        },
        "state": {
          "NULL": true
        }
      }
    },
    ":firstName": {
      "S": "updatedFirstName"
    }
  }
*/
```

Amazon DynamoDB 開發人員指南包含[更新表達式](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Expressions.UpdateExpressions.html)的詳細資訊。

#### `IgnoreNullsMode` 選項的說明
<a name="ignore-nulls-mode-descriptions"></a>
+ `IgnoreNullsMode.SCALAR_ONLY` - 使用此設定更新任何層級的純量屬性。SDK 建構的更新陳述式只會將非 Null 純量屬性傳送至 DynamoDB。SDK 會忽略 Bean 或 Map 的 null 值純量屬性，將儲存的值保留在 DynamoDB 中。

  當您更新地圖或 Bean 的純量屬性時，地圖必須已存在於 DynamoDB 中。如果您將地圖或 Bean 新增至 DynamoDB 中物件尚未存在的物件，您會取得`DynamoDbException`具有訊息的 *更新表達式中提供的文件路徑無效，無法進行更新*。在更新任何屬性之前，您必須使用 `MAPS_ONLY` 模式來新增 Bean 或映射至 DynamoDB。
+ `IgnoreNullsMode.MAPS_ONLY` - 使用此設定來新增或取代 Bean 或 Map 的屬性。SDK 會取代或新增 物件中提供的任何地圖或 Bean。會忽略物件中為 null 的任何 Bean 或映射，保留 DynamoDB 中存在的映射。
+ `IgnoreNullsMode.DEFAULT` - 使用此設定，軟體開發套件永遠不會忽略 null 值。任何 null 層級的純量屬性都會更新為 null。SDK 會將物件中任何 null 值的 Bean、地圖、清單或設定屬性更新為 DynamoDB 中的 null。當您使用此模式時，或未提供模式，因為它是預設模式，您應該先擷取項目，以便 DynamoDB 中的值不會設定為 null，而此值在物件中提供更新，除非您的意圖是將值設定為 null。

在所有模式下，如果您提供具有非空清單或設定的物件`updateItem`給 ，則清單或集合會儲存到 DynamoDB。

#### 為什麼使用 模式？
<a name="ddb-en-client-adv-features-nested-updates-nullmodes-why"></a>

當您為物件提供 Bean 或對應至 `updateItem`方法時，軟體開發套件無法判斷是否應該使用 Bean 中的屬性值 （或對應中的項目值） 來更新項目，或者整個 bean/map 是否應該取代儲存到 DynamoDB 的內容。

使用先前顯示項目先擷取的範例，讓我們嘗試更新 `city` 的屬性，`mainAddress`而不進行擷取。

```
/* The retrieval example saved the Person object with a 'mainAddress' property whose 'city' property value is "MyCity".
/* Note that we create a new Person with only the necessary information to update the city value
of the mainAddress. */
Person personForUpdate = new Person();
personForUpdate.setId(1);
// The update we want to make changes the city.
Address mainAddressForUpdate = new Address();
mainAddressForUpdate.setCity("YourCity");
personForUpdate.setMainAddress(mainAddressForUpdate);

// Lets' try the following:
Person updatedPerson = personDynamoDbTable.updateItem(personForUpdate);
/*
 Since we haven't retrieved the item, we don't know if the 'mainAddress' property
 already exists, so what update expression should the SDK generate?

A) Should it replace or add the 'mainAddress' with the provided object (setting all attributes to null other than city)
   as shown in the following simplified JSON?

      {
        "TableName": "PersonTable",
        "Key": {
          "id": {
            "N": "1"
          }
        },
        "ReturnValues": "ALL_NEW",
        "UpdateExpression": "SET #mainAddress = :mainAddress",
        "ExpressionAttributeNames": {
          "#mainAddress": "mainAddress"
        },
        "ExpressionAttributeValues": {
          ":mainAddress": {
            "M": {
              "zipCode": {
                "NULL": true
              },
              "city": {
                "S": "YourCity"
              },
              "street": {
                "NULL": true
              },
              "state": {
                "NULL": true
              }
            }
          }
        }
      }
 
B) Or should it update only the 'city' attribute of an existing 'mainAddress' as shown in the following simplified JSON?

      {
        "TableName": "PersonTable",
        "Key": {
          "id": {
            "N": "1"
          }
        },
        "ReturnValues": "ALL_NEW",
        "UpdateExpression": "SET #mainAddress.#city = :mainAddress_city",
        "ExpressionAttributeNames": {
          "#city": "city",
          "#mainAddress": "mainAddress"
        },
        "ExpressionAttributeValues": {
          ":mainAddress_city": {
            "S": "YourCity"
          }
        }
      }

However, assume that we don't know if the 'mainAddress' already exists. If it doesn't exist, the SDK would try to update 
an attribute of a non-existent map, which results in an exception.

In this particular case, we would likely select option B (SCALAR_ONLY) to retain the other values of the 'mainAddress'.
*/
```

下列兩個範例顯示 `MAPS_ONLY`和`SCALAR_ONLY`列舉值的使用。 `MAPS_ONLY` 新增地圖並`SCALAR_ONLY`更新地圖。

##### `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 值。您通常可以使用 `SCALAR_ONLY`和 ，`MAPS_ONLY`但使用 Bean 或 Map 時除外。


**軟體開發套件`updateItem`會忽略每個模式的物件中的哪些 null 值屬性？**  

| 屬性類型 | 在 SCALAR\$1ONLY 模式中 | 在 MAPS\$1ONLY 模式中 | 在 DEFAULT 模式中 | 
| --- | --- | --- | --- | 
| 最高純量 | 是 | 是 | 否 | 
| Bean 或 Map | 是 | 是 | 否 | 
| Bean 或地圖項目的純量值 | 是 1 | 否2 | 否 | 
| 列出或設定 | 是 | 是 | 否 | 

1這假設地圖已存在於 DynamoDB 中。您在物件中提供的 Bean 或對應的任何純量值，無論是否為 null，都需要 DynamoDB 中存在該值的路徑。SDK 在提交請求之前，會使用 dereference Operator `. (dot)` 建構屬性的路徑。

2 由於您使用 `MAPS_ONLY` 模式來完全取代或新增 Bean 或 Map，因此 Bean 或 Map 中的所有 null 值都會保留在儲存至 DynamoDB 的地圖中。

# 使用 保留空物件 `@DynamoDbPreserveEmptyObject`
<a name="ddb-en-client-adv-features-empty"></a>

如果您使用空白物件將 Bean 儲存到 Amazon DynamoDB，而且您希望 SDK 在擷取時重新建立空白物件，請使用 註釋內部 Bean 的 getter`@DynamoDbPreserveEmptyObject`。

為了說明註釋的運作方式，程式碼範例會使用下列兩個 Bean。

## 範例 Bean
<a name="ddb-en-client-adv-features-empty-ex1"></a>

下列資料類別包含兩個`InnerBean`欄位。getter 方法 `getInnerBeanWithoutAnno()`不會以 標註`@DynamoDbPreserveEmptyObject`。`getInnerBeanWithAnno()` 方法會加上註釋。

```
@DynamoDbBean
public class MyBean {

    private String id;
    private String name;
    private InnerBean innerBeanWithoutAnno;
    private InnerBean innerBeanWithAnno;

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

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

    public InnerBean getInnerBeanWithoutAnno() { return innerBeanWithoutAnno; }
    public void setInnerBeanWithoutAnno(InnerBean innerBeanWithoutAnno) { this.innerBeanWithoutAnno = innerBeanWithoutAnno; }

    @DynamoDbPreserveEmptyObject
    public InnerBean getInnerBeanWithAnno() { return innerBeanWithAnno; }
    public void setInnerBeanWithAnno(InnerBean innerBeanWithAnno) { this.innerBeanWithAnno = innerBeanWithAnno; }

    @Override
    public String toString() {
        return new StringJoiner(", ", MyBean.class.getSimpleName() + "[", "]")
                .add("innerBeanWithoutAnno=" + innerBeanWithoutAnno)
                .add("innerBeanWithAnno=" + innerBeanWithAnno)
                .add("id='" + id + "'")
                .add("name='" + name + "'")
                .toString();
    }
}
```

下列`InnerBean`類別的執行個體是 的欄位，`MyBean`並在範例程式碼中初始化為空物件。

```
@DynamoDbBean
public class InnerBean {

    private String innerBeanField;

    public String getInnerBeanField() {
        return innerBeanField;
    }

    public void setInnerBeanField(String innerBeanField) {
        this.innerBeanField = innerBeanField;
    }

    @Override
    public String toString() {
        return "InnerBean{" +
                "innerBeanField='" + innerBeanField + '\'' +
                '}';
    }
}
```

下列程式碼範例會將具有初始化內部 Bean 的`MyBean`物件儲存至 DynamoDB，然後擷取項目。記錄的輸出顯示 `innerBeanWithoutAnno` 未初始化，但`innerBeanWithAnno`已建立。

```
    public MyBean preserveEmptyObjectAnnoUsingGetItemExample(DynamoDbTable<MyBean> myBeanTable) {
        // Save an item to DynamoDB.
        MyBean bean = new MyBean();
        bean.setId("1");
        bean.setInnerBeanWithoutAnno(new InnerBean());   // Instantiate the inner bean.
        bean.setInnerBeanWithAnno(new InnerBean());      // Instantiate the inner bean.
        myBeanTable.putItem(bean);

        GetItemEnhancedRequest request = GetItemEnhancedRequest.builder()
                .key(Key.builder().partitionValue("1").build())
                .build();
        MyBean myBean = myBeanTable.getItem(request);

        logger.info(myBean.toString());
        // Output 'MyBean[innerBeanWithoutAnno=null, innerBeanWithAnno=InnerBean{innerBeanField='null'}, id='1', name='null']'.

        return myBean;
    }
```

## 替代靜態結構描述
<a name="ddb-en-client-adv-features-empty-ex1-static"></a>

您可以使用下列`StaticTableSchema`版本的資料表結構描述來取代 Bean 上的註釋。

```
    public static TableSchema<MyBean> buildStaticSchemas() {

        StaticTableSchema<InnerBean> innerBeanStaticTableSchema =
                StaticTableSchema.builder(InnerBean.class)
                        .newItemSupplier(InnerBean::new)
                        .addAttribute(String.class, a -> a.name("innerBeanField")
                                .getter(InnerBean::getInnerBeanField)
                                .setter(InnerBean::setInnerBeanField))
                        .build();

        return StaticTableSchema.builder(MyBean.class)
                .newItemSupplier(MyBean::new)
                .addAttribute(String.class, a -> a.name("id")
                        .getter(MyBean::getId)
                        .setter(MyBean::setId)
                        .addTag(primaryPartitionKey()))
                .addAttribute(String.class, a -> a.name("name")
                        .getter(MyBean::getName)
                        .setter(MyBean::setName))
                .addAttribute(EnhancedType.documentOf(InnerBean.class,
                                innerBeanStaticTableSchema),
                        a -> a.name("innerBean1")
                                .getter(MyBean::getInnerBeanWithoutAnno)
                                .setter(MyBean::setInnerBeanWithoutAnno))
                .addAttribute(EnhancedType.documentOf(InnerBean.class,
                                innerBeanStaticTableSchema,
                                b -> b.preserveEmptyObject(true)),
                        a -> a.name("innerBean2")
                                .getter(MyBean::getInnerBeanWithAnno)
                                .setter(MyBean::setInnerBeanWithAnno))
                .build();
    }
```

# 避免儲存巢狀物件的 null 屬性
<a name="ddb-en-client-adv-features-ignore-null"></a>

將資料類別物件儲存至 DynamoDB 時，您可以透過套用`@DynamoDbIgnoreNulls`註釋來略過巢狀物件的 null 屬性。相反地，具有 null 值的頂層屬性絕不會儲存到資料庫。

為了說明註釋的運作方式，程式碼範例會使用下列兩個 Bean。

## 範例 Bean
<a name="ddb-en-client-adv-features-ignore-null-ex1"></a>

下列資料類別包含兩個`InnerBean`欄位。不會註釋 getter 方法 `getInnerBeanWithoutAnno()`。`getInnerBeanWithIgnoreNullsAnno()` 方法會以 標註`@DynamoDbIgnoreNulls`。

```
@DynamoDbBean
public class MyBean {

    private String id;
    private String name;
    private InnerBean innerBeanWithoutAnno;
    private InnerBean innerBeanWithIgnoreNullsAnno;

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

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

    public InnerBean getInnerBeanWithoutAnno() { return innerBeanWithoutAnno; }
    public void setInnerBeanWithoutAnno(InnerBean innerBeanWithoutAnno) { this.innerBeanWithoutAnno = innerBeanWithoutAnno; }

    @DynamoDbIgnoreNulls
    public InnerBean getInnerBeanWithIgnoreNullsAnno() { return innerBeanWithIgnoreNullsAnno; }
    public void setInnerBeanWithIgnoreNullsAnno(InnerBean innerBeanWithAnno) { this.innerBeanWithIgnoreNullsAnno = innerBeanWithAnno; }

    @Override
    public String toString() {
        return new StringJoiner(", ", MyBean.class.getSimpleName() + "[", "]")
                .add("innerBeanWithoutAnno=" + innerBeanWithoutAnno)
                .add("innerBeanWithIgnoreNullsAnno=" + innerBeanWithIgnoreNullsAnno)
                .add("id='" + id + "'")
                .add("name='" + name + "'")
                .toString();
    }
}
```

下列`InnerBean`類別的執行個體是 的欄位，`MyBean`並用於下列範例程式碼。

```
@DynamoDbBean
public class InnerBean {

    private String innerBeanFieldString;
    private Integer innerBeanFieldInteger;

    public String getInnerBeanFieldString() { return innerBeanFieldString; }
    public void setInnerBeanFieldString(String innerBeanFieldString) { this.innerBeanFieldString = innerBeanFieldString; }

    public Integer getInnerBeanFieldInteger() { return innerBeanFieldInteger; }
    public void setInnerBeanFieldInteger(Integer innerBeanFieldInteger) { this.innerBeanFieldInteger = innerBeanFieldInteger; }

    @Override
    public String toString() {
        return new StringJoiner(", ", InnerBean.class.getSimpleName() + "[", "]")
                .add("innerBeanFieldString='" + innerBeanFieldString + "'")
                .add("innerBeanFieldInteger=" + innerBeanFieldInteger)
                .toString();
    }
}
```

下列程式碼範例會建立 `InnerBean` 物件，並使用 值僅設定其兩個屬性的其中之一。

```
    public void ignoreNullsAnnoUsingPutItemExample(DynamoDbTable<MyBean> myBeanTable) {
        // Create an InnerBean object and give only one attribute a value.
        InnerBean innerBeanOneAttributeSet = new InnerBean();
        innerBeanOneAttributeSet.setInnerBeanFieldInteger(200);

        // Create a MyBean instance and use the same InnerBean instance both for attributes.
        MyBean bean = new MyBean();
        bean.setId("1");
        bean.setInnerBeanWithoutAnno(innerBeanOneAttributeSet);
        bean.setInnerBeanWithIgnoreNullsAnno(innerBeanOneAttributeSet);

        Map<String, AttributeValue> itemMap = myBeanTable.tableSchema().itemToMap(bean, true);
        logger.info(itemMap.toString());
        // Log the map that is sent to the database.
        // {innerBeanWithIgnoreNullsAnno=AttributeValue(M={innerBeanFieldInteger=AttributeValue(N=200)}), id=AttributeValue(S=1), innerBeanWithoutAnno=AttributeValue(M={innerBeanFieldInteger=AttributeValue(N=200), innerBeanFieldString=AttributeValue(NUL=true)})}
        
        // Save the MyBean object to the table.
        myBeanTable.putItem(bean);
    }
```

若要視覺化傳送至 DynamoDB 的低階資料，程式碼會在儲存`MyBean`物件之前記錄屬性映射。

記錄的輸出顯示`innerBeanWithIgnoreNullsAnno`輸出一個屬性，

```
innerBeanWithIgnoreNullsAnno=AttributeValue(M={innerBeanFieldInteger=AttributeValue(N=200)})
```

`innerBeanWithoutAnno` 執行個體會輸出兩個屬性。一個屬性的值為 200，另一個是 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 是 適用於 Java 的 AWS SDK v1.x [文件 API](https://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/services/dynamodbv2/document/DynamoDB.html) 的後續版本。

**Contents**
+ [開始使用增強型文件 API](ddb-en-client-doc-api-steps.md)
  + [建立 `DocumentTableSchema`和 `DynamoDbTable`](ddb-en-client-doc-api-steps.md#ddb-en-client-doc-api-steps-createschema)
+ [建置增強型文件](ddb-en-client-doc-api-steps-create-ed.md)
  + [從 JSON 字串建置](ddb-en-client-doc-api-steps-create-ed.md#ddb-en-client-doc-api-steps-create-ed-fromJson)
  + [從個別元素建置](ddb-en-client-doc-api-steps-create-ed.md#ddb-en-client-doc-api-steps-create-ed-fromparts)
+ [執行 CRUD 操作](ddb-en-client-doc-api-steps-use.md)
+ [將增強型文件屬性作為自訂物件存取](ddb-en-client-doc-api-convert.md)
+ [使用`EnhancedDocument`不含 DynamoDB 的](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)，如本主題開頭所示。

由於增強型文件 API 的發行版本為 2.20.3 AWS SDK for Java 2.x，因此您需要該版本或更高版本。

## 建立 `DocumentTableSchema`和 `DynamoDbTable`
<a name="ddb-en-client-doc-api-steps-createschema"></a>

若要使用增強型文件 API 針對 DynamoDB 資料表叫用命令，請將資料表與用戶端 [DynamoDbTable<EnhancedDocument>](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbTable.html) 資源物件建立關聯。

增強型用戶端的 `table()`方法會建立`DynamoDbTable<EnhancedDocument>`執行個體，並需要 DynamoDB 資料表名稱和 的參數`DocumentTableSchema`。

[DocumentTableSchema](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/document/DocumentTableSchema.html) 的建置器需要主要索引鍵和一或多個屬性轉換器提供者。`AttributeConverterProvider.defaultProvider()` 方法提供[預設類型的](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/internal/converter/attribute/package-summary.html)轉換器。即使您提供自訂屬性轉換器提供者，也應該指定它。您可以將選用的次要索引鍵新增至建置器。

下列程式碼片段顯示程式碼，該程式碼會產生存放無結構描述`EnhancedDocument`物件的 DynamoDB `person`資料表的用戶端表示法。

```
DynamoDbTable<EnhancedDocument> documentDynamoDbTable = 
                enhancedClient.table("person",
                        TableSchema.documentSchemaBuilder()
                            // Specify the primary key attributes.
                            .addIndexPartitionKey(TableMetadata.primaryIndexName(),"id", AttributeValueType.S)
                            .addIndexSortKey(TableMetadata.primaryIndexName(), "lastName", AttributeValueType.S)
                            // Specify attribute converter providers. Minimally add the default one.
                            .attributeConverterProviders(AttributeConverterProvider.defaultProvider())
                            .build());
                                                         
// Call documentTable.createTable() if "person" does not exist in DynamoDB.
// createTable() should be called only one time.
```

以下顯示本節中所用`person`物件的 JSON 表示法。

### JSON `person` 物件
<a name="ddb-en-client-doc-api-steps-createschema-obj"></a>

```
{
  "id": 1,
  "firstName": "Richard",
  "lastName": "Roe",
  "age": 25,
  "addresses":
    {
      "home": {
        "zipCode": "00000",
        "city": "Any Town",
        "state": "FL",
        "street": "123 Any Street"
      },
      "work": {
        "zipCode": "00001",
        "city": "Anywhere",
        "state": "FL",
        "street": "100 Main Street"
      }
    },
  "hobbies": [
    "Hobby 1",
    "Hobby 2"
  ],
  "phoneNumbers": [
    {
      "type": "Home",
      "number": "555-0100"
    },
    {
      "type": "Work",
      "number": "555-0119"
    }
  ]
}
```

# 建置增強型文件
<a name="ddb-en-client-doc-api-steps-create-ed"></a>

`[EnhancedDocument](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/document/EnhancedDocument.html)` 代表文件類型物件，具有具有巢狀屬性的複雜結構。`EnhancedDocument` 需要最上層屬性，以符合為 指定的主索引鍵屬性`DocumentTableSchema`。其餘內容是任意的，可以包含頂層屬性和深度巢狀屬性。

您可以使用提供數種新增元素方式的建置器來建立`EnhancedDocument`執行個體。

## 從 JSON 字串建置
<a name="ddb-en-client-doc-api-steps-create-ed-fromJson"></a>

使用 JSON 字串，您可以在一個方法呼叫`EnhancedDocument`中建置 。下列程式碼片段`EnhancedDocument`會從`jsonPerson()`協助程式方法傳回的 JSON 字串建立 。`jsonPerson()` 方法會傳回先前顯示[的人員物件](ddb-en-client-doc-api-steps.md#ddb-en-client-doc-api-steps-createschema-obj)的 JSON 字串版本。

```
EnhancedDocument document = 
        EnhancedDocument.builder()
                        .json( jsonPerson() )
                        .build());
```

## 從個別元素建置
<a name="ddb-en-client-doc-api-steps-create-ed-fromparts"></a>

或者，您可以使用建置器的類型安全方法，從個別元件建置`EnhancedDocument`執行個體。

下列範例會建置`person`增強型文件，類似於上一個範例中從 JSON 字串建置的增強型文件。

```
        /* Define the shape of an address map whose JSON representation looks like the following.
           Use 'addressMapEnhancedType' in the following EnhancedDocument.builder() to simplify the code.
           "home": {
             "zipCode": "00000",
             "city": "Any Town",
             "state": "FL",
             "street": "123 Any Street"
           }*/
        EnhancedType<Map<String, String>> addressMapEnhancedType =
                EnhancedType.mapOf(EnhancedType.of(String.class), EnhancedType.of(String.class));


        //  Use the builder's typesafe methods to add elements to the enhanced document.
        EnhancedDocument personDocument = EnhancedDocument.builder()
                .putNumber("id", 50)
                .putString("firstName", "Shirley")
                .putString("lastName", "Rodriguez")
                .putNumber("age", 53)
                .putNull("nullAttribute")
                .putJson("phoneNumbers", phoneNumbersJSONString())
                /* Add the map of addresses whose JSON representation looks like the following.
                        {
                          "home": {
                            "zipCode": "00000",
                            "city": "Any Town",
                            "state": "FL",
                            "street": "123 Any Street"
                          }
                        } */
                .putMap("addresses", getAddresses(), EnhancedType.of(String.class), addressMapEnhancedType)
                .putList("hobbies", List.of("Theater", "Golf"), EnhancedType.of(String.class))
                .build();
```

### 協助程式方法
<a name="ddb-en-client-doc-api-steps-use-fromparts-helpers"></a>

```
    private static String phoneNumbersJSONString() {
        return "  [" +
                "    {" +
                "      \"type\": \"Home\"," +
                "      \"number\": \"555-0140\"" +
                "    }," +
                "    {" +
                "      \"type\": \"Work\"," +
                "      \"number\": \"555-0155\"" +
                "    }" +
                "  ]";
    }

    private static Map<String, Map<String, String>> getAddresses() {
        return Map.of(
                "home", Map.of(
                        "zipCode", "00002",
                        "city", "Any Town",
                        "state", "ME",
                        "street", "123 Any Street"));

    }
```

# 執行 CRUD 操作
<a name="ddb-en-client-doc-api-steps-use"></a>

定義`EnhancedDocument`執行個體之後，您可以將其儲存至 DynamoDB 資料表。下列程式碼片段使用從個別元素建立的 [personDocument](ddb-en-client-doc-api-steps-create-ed.md#ddb-en-client-doc-api-steps-create-ed-fromparts)。

```
documentDynamoDbTable.putItem(personDocument);
```

從 DynamoDB 讀取增強型文件執行個體後，您可以使用 getter 擷取個別屬性值，如下列程式碼片段所示，以存取從 儲存的資料`personDocument`。或者，您可以將完整內容擷取至 JSON 字串，如範例程式碼的最後部分所示。

```
        // Read the item.
        EnhancedDocument personDocFromDb = documentDynamoDbTable.getItem(Key.builder().partitionValue(50).build());

        // Access top-level attributes.
        logger.info("Name: {} {}", personDocFromDb.getString("firstName"), personDocFromDb.getString("lastName"));
        // Name: Shirley Rodriguez

        // Typesafe access of a deeply nested attribute. The addressMapEnhancedType shown previously defines the shape of an addresses map.
        Map<String, Map<String, String>> addresses = personDocFromDb.getMap("addresses", EnhancedType.of(String.class), addressMapEnhancedType);
        addresses.keySet().forEach(k -> logger.info(addresses.get(k).toString()));
        // {zipCode=00002, city=Any Town, street=123 Any Street, state=ME}

        // Alternatively, work with AttributeValue types checking along the way for deeply nested attributes.
        Map<String, AttributeValue> addressesMap = personDocFromDb.getMapOfUnknownType("addresses");
        addressesMap.keySet().forEach((String k) -> {
            logger.info("Looking at data for [{}] address", k);
            // Looking at data for [home] address
            AttributeValue value = addressesMap.get(k);
            AttributeValue cityValue = value.m().get("city");
            if (cityValue != null) {
                logger.info(cityValue.s());
                // Any Town
            }
        });

        List<AttributeValue> phoneNumbers = personDocFromDb.getListOfUnknownType("phoneNumbers");
        phoneNumbers.forEach((AttributeValue av) -> {
            if (av.hasM()) {
                AttributeValue type = av.m().get("type");
                if (type.s() != null) {
                    logger.info("Type of phone: {}", type.s());
                    // Type of phone: Home
                    // Type of phone: Work
                }
            }
        });

        String jsonPerson = personDocFromDb.toJson();
        logger.info(jsonPerson);
        // {"firstName":"Shirley","lastName":"Rodriguez","addresses":{"home":{"zipCode":"00002","city":"Any Town","street":"123 Any Street","state":"ME"}},"hobbies":["Theater","Golf"],
        //     "id":50,"nullAttribute":null,"age":53,"phoneNumbers":[{"number":"555-0140","type":"Home"},{"number":"555-0155","type":"Work"}]}
```

`EnhancedDocument` 執行個體可與 `[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 使用[控制屬性轉換](ddb-en-client-adv-features-conversion.md)區段中顯示的 `AttributeConverterProvider`和 `AttributeConverter`，作為 DynamoDB 增強型用戶端 API 的一部分。

在下列範例中，我們使用 `CustomAttributeConverterProvider`搭配其巢狀`AddressConverter`類別來轉換`Address`物件。

此範例顯示您可以混合來自 類別的資料，也可以混合來自視需要建置之結構的資料。此範例也顯示自訂類別可用於巢狀結構的任何層級。此範例中的`Address`物件是映射中使用的值。

```
    public static void attributeToAddressClassMappingExample(DynamoDbEnhancedClient enhancedClient, DynamoDbClient standardClient) {
        String tableName = "customer";

        // Define the DynamoDbTable for an enhanced document.
        // The schema builder provides methods for attribute converter providers and keys.
        DynamoDbTable<EnhancedDocument> documentDynamoDbTable = enhancedClient.table(tableName,
                DocumentTableSchema.builder()
                        // Add the CustomAttributeConverterProvider along with the default when you build the table schema.
                        .attributeConverterProviders(
                                List.of(
                                        new CustomAttributeConverterProvider(),
                                        AttributeConverterProvider.defaultProvider()))
                        .addIndexPartitionKey(TableMetadata.primaryIndexName(), "id", AttributeValueType.N)
                        .addIndexSortKey(TableMetadata.primaryIndexName(), "lastName", AttributeValueType.S)
                        .build());
        // Create the DynamoDB table if needed.
        documentDynamoDbTable.createTable();
        waitForTableCreation(tableName, standardClient);


        // The getAddressesForCustomMappingExample() helper method that provides 'addresses' shows the use of a custom Address class
        // rather than using a Map<String, Map<String, String> to hold the address data.
        Map<String, Address> addresses = getAddressesForCustomMappingExample();

        // Build an EnhancedDocument instance to save an item with a mix of structures defined as needed and static classes.
        EnhancedDocument personDocument = EnhancedDocument.builder()
                .putNumber("id", 50)
                .putString("firstName", "Shirley")
                .putString("lastName", "Rodriguez")
                .putNumber("age", 53)
                .putNull("nullAttribute")
                .putJson("phoneNumbers", phoneNumbersJSONString())
                // Note the use of 'EnhancedType.of(Address.class)' instead of the more generic
                // 'EnhancedType.mapOf(EnhancedType.of(String.class), EnhancedType.of(String.class))' that was used in a previous example.
                .putMap("addresses", addresses, EnhancedType.of(String.class), EnhancedType.of(Address.class))
                .putList("hobbies", List.of("Hobby 1", "Hobby 2"), EnhancedType.of(String.class))
                .build();
        // Save the item to DynamoDB.
        documentDynamoDbTable.putItem(personDocument);

        // Retrieve the item just saved.
        EnhancedDocument srPerson = documentDynamoDbTable.getItem(Key.builder().partitionValue(50).sortValue("Rodriguez").build());

        // Access the addresses attribute.
        Map<String, Address> srAddresses = srPerson.get("addresses",
                EnhancedType.mapOf(EnhancedType.of(String.class), EnhancedType.of(Address.class)));

        srAddresses.keySet().forEach(k -> logger.info(addresses.get(k).toString()));

        documentDynamoDbTable.deleteTable();

// The content logged to the console shows that the saved maps were converted to Address instances.
Address{street='123 Main Street', city='Any Town', state='NC', zipCode='00000'}
Address{street='100 Any Street', city='Any Town', state='NC', zipCode='00000'}
```

## `CustomAttributeConverterProvider`代碼
<a name="ddb-en-client-doc-api-convert-provider"></a>

```
public class CustomAttributeConverterProvider implements AttributeConverterProvider {

    private final Map<EnhancedType<?>, AttributeConverter<?>> converterCache = ImmutableMap.of(
            // 1. Add AddressConverter to the internal cache.
            EnhancedType.of(Address.class), new AddressConverter());

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

    // 2. The enhanced client queries the provider for attribute converters if it
    //    encounters a type that it does not know how to convert.
    @SuppressWarnings("unchecked")
    @Override
    public <T> AttributeConverter<T> converterFor(EnhancedType<T> enhancedType) {
        return (AttributeConverter<T>) converterCache.get(enhancedType);
    }

    // 3. Custom attribute converter
    private class AddressConverter implements AttributeConverter<Address> {
        // 4. Transform an Address object into a DynamoDB map.
        @Override
        public AttributeValue transformFrom(Address address) {

            Map<String, AttributeValue> attributeValueMap = Map.of(
                    "street", AttributeValue.fromS(address.getStreet()),
                    "city", AttributeValue.fromS(address.getCity()),
                    "state", AttributeValue.fromS(address.getState()),
                    "zipCode", AttributeValue.fromS(address.getZipCode()));

            return AttributeValue.fromM(attributeValueMap);
        }

        // 5. Transform the DynamoDB map attribute to an Address oject.
        @Override
        public Address transformTo(AttributeValue attributeValue) {
            Map<String, AttributeValue> m = attributeValue.m();
            Address address = new Address();
            address.setStreet(m.get("street").s());
            address.setCity(m.get("city").s());
            address.setState(m.get("state").s());
            address.setZipCode(m.get("zipCode").s());

            return address;
        }

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

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

## `Address` 類別
<a name="ddb-en-client-doc-api-convert-address"></a>

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

                  public Address() {
                  }

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

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

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

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

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

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

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

                  public void setZipCode(String zipCode) {
                  this.zipCode = zipCode;
                  }
                  }
```

## 提供地址的協助程式方法
<a name="ddb-en-client-doc-api-convert-helper"></a>

下列協助程式方法提供使用自訂`Address`執行個體做為值的映射，而非一般`Map<String, String>`執行個體做為值。

```
    private static Map<String, Address> getAddressesForCustomMappingExample() {
        Address homeAddress = new Address();
        homeAddress.setStreet("100 Any Street");
        homeAddress.setCity("Any Town");
        homeAddress.setState("NC");
        homeAddress.setZipCode("00000");

        Address workAddress = new Address();
        workAddress.setStreet("123 Main Street");
        workAddress.setCity("Any Town");
        workAddress.setState("NC");
        workAddress.setZipCode("00000");

        return Map.of("home", homeAddress,
                "work", workAddress);
    }
```

# 使用`EnhancedDocument`不含 DynamoDB 的
<a name="ddb-en-client-doc-api-standalone"></a>

雖然您通常使用 執行個體`EnhancedDocument`來讀取和寫入文件類型的 DynamoDB 項目，但它也可以獨立於 DynamoDB 使用。

您可以使用 `EnhancedDocuments` 在 JSON 字串或自訂物件之間轉換為低階映射的 ，`AttributeValues`如下列範例所示。

```
    public static void conversionWithoutDynamoDbExample() {
        Address address = new Address();
        address.setCity("my city");
        address.setState("my state");
        address.setStreet("my street");
        address.setZipCode("00000");

        // Build an EnhancedDocument instance for its conversion functionality alone.
        EnhancedDocument addressEnhancedDoc = EnhancedDocument.builder()
                // Important: You must specify attribute converter providers when you build an EnhancedDocument instance not used with a DynamoDB table.
                .attributeConverterProviders(new CustomAttributeConverterProvider(), DefaultAttributeConverterProvider.create())
                .put("addressDoc", address, Address.class)
                .build();

        // Convert address to a low-level item representation.
        final Map<String, AttributeValue> addressAsAttributeMap = addressEnhancedDoc.getMapOfUnknownType("addressDoc");
        logger.info("addressAsAttributeMap: {}", addressAsAttributeMap.toString());

        // Convert address to a JSON string.
        String addressAsJsonString = addressEnhancedDoc.getJson("addressDoc");
        logger.info("addressAsJsonString: {}", addressAsJsonString);
        // Convert addressEnhancedDoc back to an Address instance.
        Address addressConverted =  addressEnhancedDoc.get("addressDoc", Address.class);
        logger.info("addressConverted: {}", addressConverted.toString());
    }

   /* Console output:
          addressAsAttributeMap: {zipCode=AttributeValue(S=00000), state=AttributeValue(S=my state), street=AttributeValue(S=my street), city=AttributeValue(S=my city)}
          addressAsJsonString: {"zipCode":"00000","state":"my state","street":"my street","city":"my city"}
          addressConverted: Address{street='my street', city='my city', state='my state', zipCode='00000'}
   */
```

**注意**  
當您使用獨立於 DynamoDB 資料表的增強型文件時，請務必在建置器上明確設定屬性轉換器提供者。  
相反地，當增強型文件與 DynamoDB 資料表搭配使用時，文件資料表結構描述會提供轉換器提供者。

# 使用擴充功能自訂 DynamoDB 增強型用戶端操作
<a name="ddb-en-client-extensions"></a>

DynamoDB 增強型用戶端 API 支援外掛程式擴充功能，可提供映射操作以外的功能。延伸模組使用兩種勾點方法來修改讀取和寫入操作期間的資料：
+ `beforeWrite()` - 在寫入操作發生之前對其進行修改
+ `afterRead()` - 修改讀取操作發生後的結果

有些操作 （例如項目更新） 同時執行寫入和讀取，因此會呼叫這兩種勾點方法。

## 如何載入延伸模組
<a name="ddb-en-client-extensions-loading"></a>

延伸項目會依照您在增強型用戶端建置器中指定的順序載入。負載順序可能很重要，因為一個延伸模組可以對先前延伸模組轉換的值採取行動。

根據預設，增強型用戶端會載入兩個延伸模組：
+ `[VersionedRecordExtension](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/extensions/VersionedRecordExtension.html)` - 提供樂觀鎖定
+ `[AtomicCounterExtension](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/extensions/AtomicCounterExtension.html)` - 自動遞增計數器屬性

您可以使用增強型用戶端建置器覆寫預設行為，並載入任何延伸。如果您不想要預設副檔名，也可以不指定任何副檔名。

**重要**  
如果您載入自己的擴充功能，增強型用戶端不會載入任何預設擴充功能。如果您想要任一預設延伸模組提供的行為，您需要將其明確新增至延伸模組清單。

下列範例顯示如何載入名為 `verifyChecksumExtension`的自訂延伸`VersionedRecordExtension`模組。此範例中`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` 延伸項目會在項目寫入資料庫時遞增和追蹤項目版本號碼，以提供樂觀的鎖定。如果實際持續項目的版本編號與應用程式上次讀取的值不符，則每個寫入都會新增一個條件，導致寫入失敗。

#### Configuration
<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>

使用 的樂觀鎖定對這些 `DynamoDbEnhancedClient`和 `DynamoDbTable`方法`VersionedRecordExtension`有下列影響：

**`putItem`**  
新項目會獲指派初始版本值 0。這可以使用 設定`@DynamoDbVersionAttribute(startAt = X)`。

**`updateItem`**  
如果您擷取項目、更新其一或多個屬性，並嘗試儲存變更，則只有在用戶端和伺服器端的版本編號相符時，操作才會成功。  
如果成功，版本編號會自動遞增 1。這可以使用 設定`@DynamoDbVersionAttribute(incrementBy = X)`。

**`deleteItem`**  
`DynamoDbVersionAttribute` 註釋沒有效果。刪除項目時，您必須手動新增條件表達式。  
下列範例新增條件式表達式，以確保已刪除的項目是已讀取的項目。在下列範例中`recordVersion`， Bean 的 屬性以 標註`@DynamoDbVersionAttribute`。  

```
// 1. Read the item and get its current version.
Customer item = customerTable.getItem(Key.builder().partitionValue("someId").build());
// `recordVersion` is the bean's attribute that is annotated with `@DynamoDbVersionAttribute`.
AttributeValue currentVersion = item.getRecordVersion();

// 2. Create conditional delete with the `currentVersion` value.
DeleteItemEnhancedRequest deleteItemRequest =
    DeleteItemEnhancedRequest.builder()
       .key(KEY)
       .conditionExpression(Expression.builder()
           .expression("recordVersion = :current_version_value")
           .putExpressionValue(":current_version_value", currentVersion)
           .build()).build();

customerTable.deleteItem(deleteItemRequest);
```

**`transactWriteItems`**  
+ `addPutItem`：此方法具有與 相同的行為`putItem`。
+ `addUpdateItem`：此方法具有與 相同的行為`updateItem`。
+ `addDeleteItem`：此方法具有與 相同的行為`deleteItem`。

**`batchWriteItem`**  
+ `addPutItem`：此方法具有與 相同的行為`putItem`。
+ `addDeleteItem`：此方法具有與 相同的行為`deleteItem`。

**注意**  
DynamoDB 全域資料表在並行更新之間使用[「最後寫入器獲勝」對帳](https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/V2globaltables_HowItWorks.html#V2globaltables_HowItWorks.consistency-modes)，其中 DynamoDB 會盡最大努力判斷最後寫入器。如果您使用全域資料表，此「上次寫入器獲勝」政策表示鎖定策略可能無法如預期運作，因為所有複本最終會根據 DynamoDB 決定的上次寫入收斂。

#### 如何停用
<a name="ddb-en-client-extensions-VRE-how-to-disable"></a>

若要停用樂觀鎖定，請勿使用 `@DynamoDbVersionAttribute`註釋。

### 使用 實作計數器 `AtomicCounterExtension`
<a name="ddb-en-client-extensions-ACE"></a>

每次記錄寫入資料庫時，`AtomicCounterExtension`延伸項目都會遞增標記的數值屬性。您可以指定開始值和增量值。如果未指定任何值，則開始值會設為 0，而屬性的值會遞增 1。

#### Configuration
<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)`使用目前時間戳記更新 類型的標記屬性。預設不會載入此延伸模組。

#### Configuration
<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`。預設不會載入此延伸模組。

#### Configuration
<a name="ddb-en-client-extensions-AGUE-conf"></a>

`uniqueId` 屬性是下列程式碼片段中延伸項目行為的目標。

```
    @AutoGeneratedUuidExtension
    public String getUniqueId() {...}
    public void setUniqueId(String uniqueId) {...}
```

等效的靜態資料表結構描述方法會顯示在下列程式碼片段中。

```
     .addAttribute(String.class, a -> a.name("uniqueId")
                                        .getter(Customer::getUniqueId)
                                        .setter(Customer::setUniqueId)
                                        // Applying the 'autoGeneratedUuid' tag to the attribute.
                                        .tags(AutoGeneratedUuidExtension.AttributeTags.autoGeneratedUuidAttribute())
```

如果您希望延伸模組僅針對 `putItem` 方法填入 UUID，而非針對 `updateItem`方法填入 UUID，請新增[更新行為](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/mapper/UpdateBehavior.html)註釋，如下列程式碼片段所示。

```
    @AutoGeneratedUuidExtension
    @DynamoDbUpdateBehavior(UpdateBehavior.WRITE_IF_NOT_EXISTS)
    public String getUniqueId() {...}
    public void setUniqueId(String uniqueId) {...}
```

如果您使用靜態資料表結構描述方法，請使用下列同等程式碼。

```
     .addAttribute(String.class, a -> a.name("uniqueId")
                                        .getter(Customer::getUniqueId)
                                        .setter(Customer::setUniqueId)
                                        // Applying the 'autoGeneratedUuid' tag to the attribute.
                                        .tags(AutoGeneratedUuidExtension.AttributeTags.autoGeneratedUuidAttribute(),
                                              StaticAttributeTags.updateBehavior(UpdateBehavior.WRITE_IF_NOT_EXISTS))
```

# 自訂延伸模組範例
<a name="ddb-en-client-extensions-custom"></a>

您可以實作 `DynamoDbEnhancedClientExtension`界面來建立自訂擴充功能。下列自訂延伸類別顯示一種`beforeWrite()`方法，如果資料庫中的項目還沒有`registrationDate`更新表達式，則使用更新表達式來設定屬性。

```
public final class CustomExtension implements DynamoDbEnhancedClientExtension {

    // 1. In a custom extension, use an UpdateExpression to define what action to take before
    //    an item is updated.
    @Override
    public WriteModification beforeWrite(DynamoDbExtensionContext.BeforeWrite context) {
        if ( context.operationContext().tableName().equals("Customer")
                && context.operationName().equals(OperationName.UPDATE_ITEM)) {
            return WriteModification.builder()
                    .updateExpression(createUpdateExpression())
                    .build();
        }
        return WriteModification.builder().build();  // Return an "empty" WriteModification instance if the extension should not be applied.
                                                     // In this case, if the code is not updating an item on the Customer table.
    }

    private static UpdateExpression createUpdateExpression() {

        // 2. Use a SetAction, a subclass of UpdateAction, to provide the values in the update.
        SetAction setAction =
                SetAction.builder()
                        .path("registrationDate")
                        .value("if_not_exists(registrationDate, :regValue)")
                        .putExpressionValue(":regValue", AttributeValue.fromS(Instant.now().toString()))
                        .build();
        // 3. Build the UpdateExpression with one or more UpdateAction.
        return UpdateExpression.builder()
                .addAction(setAction)
                .build();
    }
}
```

# 非同步使用 DynamoDB 增強型用戶端 API
<a name="ddb-en-client-async"></a>

如果您的應用程式需要非封鎖、非同步呼叫 DynamoDB，您可以使用 [DynamoDbEnhancedAsyncClient](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/DynamoDbEnhancedAsyncClient.html)。它類似於同步實作，但有下列主要差異：

1. 建置 時`DynamoDbEnhancedAsyncClient`，您必須提供標準用戶端的非同步版本 `DynamoDbAsyncClient`，如下列程式碼片段所示。

   ```
    DynamoDbEnhancedAsyncClient enhancedClient = 
        DynamoDbEnhancedAsyncClient.builder()
                                   .dynamoDbClient(dynamoDbAsyncClient)
                                   .build();
   ```

1. 傳回單一資料物件的方法會傳回結果`CompletableFuture`的 ，而非僅傳回結果。然後，您的應用程式可以執行其他工作，而不必封鎖結果。下列程式碼片段顯示非同步`getItem()`方法。

   ```
   CompletableFuture<Customer> result = customerDynamoDbTable.getItem(customer);
   // Perform other work here.
   return result.join();   // Now block and wait for the result.
   ```

1. 傳回結果分頁清單的方法會傳回[https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/pagination/sync/SdkIterable.html](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/core/pagination/sync/SdkIterable.html)同步針對相同方法`DynamoDbEnhanceClient`傳回的 ，[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/zh_tw/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/zh_tw/sdk-for-java/latest/developer-guide/ddb-en-client-anno-index.html)  | 
| DynamoDbIgnore | 屬性 |  導致屬性保持未映射。  |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_tw/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/zh_tw/sdk-for-java/latest/developer-guide/ddb-en-client-anno-index.html)  | 
| DynamoDbPartitionKey | 屬性 |  將屬性標記為 DynamoDb 資料表的主要分割區索引鍵 （雜湊索引鍵）。  |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_tw/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/zh_tw/sdk-for-java/latest/developer-guide/ddb-en-client-anno-index.html)  | 
| DynamoDbSecondarySortKey | 屬性 |  將屬性標記為全域或本機次要索引的選用排序索引鍵。  |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_tw/sdk-for-java/latest/developer-guide/ddb-en-client-anno-index.html)  | 
| DynamoDbSortKey | 屬性 |  將屬性標記為選用的主要排序索引鍵 （範圍索引鍵）。  |  [\[See the AWS documentation website for more details\]](http://docs.aws.amazon.com/zh_tw/sdk-for-java/latest/developer-guide/ddb-en-client-anno-index.html)  | 
| DynamoDbUpdateBehavior | 屬性 |  指定在 UpdateItem 等 'update' 操作中更新此屬性時的行為。  | [簡介和範例。](ddb-en-client-adv-features-upd-behavior.md) | 
| DynamoDbVersionAttribute | 屬性 | 遞增項目版本號碼。 | [簡介和討論。](ddb-en-client-extensions.md#ddb-en-client-extensions-VRE) | 

1您可以將屬性層級註釋套用至 getter 或 setter，但不能同時套用兩者。本指南顯示 getter 上的註釋。

2 該術語`property`通常用於封裝在 JavaBean 資料類別中的值。不過，本指南會`attribute`改用 一詞，以符合 DynamoDB 所使用的術語。