

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

# 了解 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 之後的程式碼未投影訊息屬性。