

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

# 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());
    }
```