DynamoDB Enhanced Client API の基礎について学ぶ - AWS SDK for Java 2.x

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

DynamoDB Enhanced Client API の基礎について学ぶ

このトピックでは、DynamoDB Enhanced Client API の基本機能について説明し、標準の DynamoDB client API と比較します。

DynamoDB Enhanced Client API を初めて使用する場合は、入門チュートリアルを読んで基本的なクラスに慣れることをおすすめします。

Java の DynamoDB アイテム

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

ユースケースで一貫性のある属性セットを持つアイテムが必要な場合は、注釈付きクラスを使用するか、ビルダーを使用して適切な静的型 TableSchema を生成します。

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

データモデルクラスの属性タイプ

DynamoDB は、Java の豊富なタイプシステムに比べて少数の属性型をサポートしています。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、、インスタント BigDecimalなど、多数のタイプの属性コンバーターをサポートします。 https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/enhanced/dynamodb/internal/converter/attribute/InstantAsStringAttributeConverter.htmlリストは、 AttributeConverter インターフェイス の既知の実装クラスに表示されます。リストには、マップ、リスト、セットなど、さまざまなタイプとコレクションが含まれています。

デフォルトでサポートされていない、または JavaBean 規則に準拠していない属性タイプのデータを保存するには、変換を実行するカスタムAttributeConverter実装を記述できます。については、「属性変換」セクションを参照してください。

クラスが Java Bean 仕様に準拠する属性型 (または不変データクラス) のデータを保存するには、2 つの方法があります。

  • ソースファイルにアクセスできる場合は、クラスに @DynamoDbBean (または @DynamoDbImmutable) という注釈を付けることができます。ネストされた属性について説明しているセクションでは、注釈付きクラスの使用を示しています。

  • 属性 JavaBean のデータクラスのソースファイルにアクセスできない場合 (またはアクセス可能なクラスのソースファイルに注釈を付けたくない場合)、ビルダーアプローチを使用できます。これにより、キーを定義せずにテーブルスキーマが作成されます。次に、このテーブルスキーマを別のテーブルスキーマ内にネストしてマッピングを実行できます。ネストされた属性セクションには、ネストされたスキーマの使用例があります。

Null 値

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

updateItem リクエストの場合、NULL 値の属性はデータベース上のアイテムから削除されます。一部の属性値を更新し、他の属性値はそのままにしておきたい場合は、変更してはいけない他の属性の値をコピーするか、更新ビルダーの ignoreNull () メソッドを使用してください。

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

public void updateItemNullsExample(){ Customer customer = new Customer(); customer.setCustName("CustName"); customer.setEmail("email"); customer.setId("1"); customer.setRegistrationDate(Instant.now()); // Put item with values for all attributes. customerDynamoDbTable.putItem(customer); // Create a Customer instance with the same id value, but a different name value. // Do not set the 'registrationDate' attribute. Customer custForUpdate = new Customer(); custForUpdate.setCustName("NewName"); custForUpdate.setEmail("email"); custForUpdate.setId("1"); // Update item without setting the registrationDate attribute. customerDynamoDbTable.updateItem(b -> b .item(custForUpdate) .ignoreNulls(Boolean.TRUE)); Customer updatedWithNullsIgnored = customerDynamoDbTable.getItem(customer); // registrationDate value is unchanged. logger.info(updatedWithNullsIgnored.toString()); customerDynamoDbTable.updateItem(custForUpdate); Customer updatedWithNulls = customerDynamoDbTable.getItem(customer); // registrationDate value is null because ignoreNulls() was not used. logger.info(updatedWithNulls.toString()); } } // Logged lines. Customer [id=1, custName=NewName, email=email, registrationDate=2023-04-05T16:32:32.056Z] Customer [id=1, custName=NewName, email=email, registrationDate=null]

DynamoDB Enhanced Client API の基本メソッド

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

この例では前に示した Customer クラス を使用しています。

// 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 クライアントと比較する

DynamoDB client API (標準拡張) はどちらも、DynamoDB テーブルを操作して CRUD (作成、読み取り、更新、削除) データレベルの操作を実行できます。クライアント API の違いは、その方法にあります。標準クライアントを使用すると、低レベルのデータ属性を直接操作できます。拡張クライアント API では、使い慣れた Java クラスが使用され、バックグラウンドで低レベル API にマッピングされます。

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

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

比較: 標準の DynamoDB クライアントを使用してテーブルを作成

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 を使用したテーブルの作成

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 で不変クラスを使用する例です。マッピングされたデータクラスでの不変クラスの使用については、このトピックの後半で説明します

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

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