

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

// PutItem
customerTable.putItem(customer);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

import java.time.Instant;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        @Override
        public void onError(Throwable throwable) {
        }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

import java.util.Objects;

@DynamoDbBean
public class MovieActor implements Comparable<MovieActor> {

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

import java.util.List;
```

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

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

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

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

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

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

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

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

    public String getLastPostedBy() {
        return LastPostedBy;
    }

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

    public Integer getViews() {
        return Views;
    }

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

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

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

    public Integer getAnswered() {
        return Answered;
    }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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