aws-sdk-java-v2-dynamodb について
AWS SDK for Java 2.x を使う Java 開発者向けの DynamoDB スキルです。Enhanced Client による型安全なデータ操作を中心に、クエリ、一括処理、トランザクション、Spring Boot 連携を扱います。
主な機能
- 01Enhanced Client による型安全なマッピング
- 02CRUD と条件付きクエリ
- 03一括読み書きとトランザクション
- 04Spring Boot リポジトリー連携
- 05失敗時の再試行とテストパターン
活用シーン
- 01DynamoDB で型安全な CRUD 操作を実行
- 02パーティションキーや GSI によるテーブル検索
- 03複数テーブルにまたがる原子的なトランザクション
- 04DynamoDB を Spring Boot リポジトリーに統合
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ giuseppe-trisciuoglio/ developer-kit --skill aws-sdk-java-v2-dynamodbインストール後、Claude Code で/aws-sdk-java-v2-dynamodbを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/advanced-operations.mdentity-mapping.mdspring-boot-integration.mdtesting-strategies.md詳細
AWS SDK for Java 2.x - Amazon DynamoDB
概要
AWS SDK for Java 2.x の Enhanced Client を使用して、型安全な CRUD、クエリ、バッチ操作、トランザクション、Spring Boot との統合を含む DynamoDB のパターンを提供します。
使用する場面
- DynamoDB 項目の CRUD 操作
- ソートキーまたは GSI を使用したテーブルのクエリ
- 複数の項目に対するバッチ操作
- テーブル間のアトミックトランザクション
- DynamoDB との Spring Boot 統合
手順
pom.xmlに AWS SDK DynamoDB の依存関係を追加します- クライアントのセットアップを構成します(低レベルまたは Enhanced Client)
@DynamoDbBeanアノテーションを付けたエンティティクラスを定義しますDynamoDbTableを使って操作を実行します(CRUD、クエリ、スキャン、バッチ、トランザクション)- 再試行ロジックと指数バックオフで部分的な失敗に対処します
- Spring Boot との統合にはリポジトリパターンを使用します
依存関係
pom.xml に追加します:
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>dynamodb</artifactId>
</dependency>
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>dynamodb-enhanced</artifactId>
</dependency>
クライアントのセットアップ
低レベルクライアント
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
DynamoDbClient dynamoDb = DynamoDbClient.builder()
.region(Region.US_EAST_1)
.build();
Enhanced Client(推奨)
import software.amazon.awssdk.enhanced.dynamodb.DynamoDbEnhancedClient;
DynamoDbEnhancedClient enhancedClient = DynamoDbEnhancedClient.builder()
.dynamoDbClient(dynamoDb)
.build();
エンティティのマッピング
@DynamoDbBean
public class Customer {
@DynamoDbPartitionKey
private String customerId;
@DynamoDbAttribute("customer_name")
private String name;
private String email;
@DynamoDbSortKey
private String orderId;
// Getters and setters
}
GSI やカスタムコンバーターを使った複雑なエンティティマッピングについては、Entity Mapping Reference を参照してください。
CRUD 操作
基本操作
// Create or update item
DynamoDbTable<Customer> table = enhancedClient.table("Customers", TableSchema.fromBean(Customer.class));
table.putItem(customer);
// Get item
Customer result = table.getItem(Key.builder().partitionValue(customerId).build());
// Update item
return table.updateItem(customer);
// Delete item
table.deleteItem(Key.builder().partitionValue(customerId).build());
複合キー操作
// Get item with composite key
Order order = table.getItem(Key.builder()
.partitionValue(customerId)
.sortValue(orderId)
.build());
クエリ操作
基本的なクエリ
import software.amazon.awssdk.enhanced.dynamodb.model.QueryConditional;
QueryConditional queryConditional = QueryConditional
.keyEqualTo(Key.builder()
.partitionValue(customerId)
.build());
List<Order> orders = table.query(queryConditional).items().stream()
.collect(Collectors.toList());
フィルターを使った高度なクエリ
import software.amazon.awssdk.enhanced.dynamodb.Expression;
Expression filter = Expression.builder()
.expression("status = :pending")
.putExpressionValue(":pending", AttributeValue.builder().s("PENDING").build())
.build();
List<Order> pendingOrders = table.query(r -> r
.queryConditional(queryConditional)
.filterExpression(filter))
.items().stream()
.collect(Collectors.toList());
クエリパターンの詳細については、Advanced Operations Reference を参照してください。
スキャン操作
Warning: スキャンはテーブル全体を読み取り、すべての項目について読み込みキャパシティを消費します。可能な限り、パーティションキーまたは GSI を使ったクエリ操作を優先してください。
スキャン前の確認事項:
- アクセスパターンに対して、パーティションキーを使ったクエリが実行できないことを確認します
- テーブルに十分なプロビジョニング済み読み込みキャパシティがあることを確認するか、オンデマンドモードを使用します
- キャパシティ消費を制御するため、
limit()によるページネーションの使用を検討します
// Scan all items
List<Customer> allCustomers = table.scan().items().stream()
.collect(Collectors.toList());
// Scan with filter
Expression filter = Expression.builder()
.expression("points >= :minPoints")
.putExpressionValue(":minPoints", AttributeValue.builder().n("1000").build())
.build();
List<Customer> vipCustomers = table.scan(r -> r.filterExpression(filter))
.items().stream()
.collect(Collectors.toList());
バッチ操作
バッチ取得
import software.amazon.awssdk.enhanced.dynamodb.model.*;
List<Key> keys = customerIds.stream()
.map(id -> Key.builder().partitionValue(id).build())
.collect(Collectors.toList());
ReadBatch.Builder<Customer> batchBuilder = ReadBatch.builder(Customer.class)
.mappedTableResource(table);
keys.forEach(batchBuilder::addGetItem);
BatchGetResultPageIterable result = enhancedClient.batchGetItem(r ->
r.addReadBatch(batchBuilder.build()));
List<Customer> customers = result.resultsForTable(table).stream()
.collect(Collectors.toList());
エラー処理を伴うバッチ書き込み
WriteBatch.Builder<Customer> batchBuilder = WriteBatch.builder(Customer.class)
.mappedTableResource(table);
customers.forEach(batchBuilder::addPutItem);
BatchWriteItemEnhancedRequest request = BatchWriteItemEnhancedRequest.builder()
.addWriteBatch(batchBuilder.build())
.build();
BatchWriteResult result = enhancedClient.batchWriteItem(request);
// Validate: check for unprocessed items
if (!result.writeResponsesForTable(table).isEmpty()) {
// Retry unprocessed items with exponential backoff
Map<String, AttributeValue> unprocessed = result.writeResponsesForTable(table).get(0)
.unprocessedAttributes();
if (unprocessed != null && !unprocessed.isEmpty()) {
enhancedClient.batchWriteItem(r -> r
.addWriteBatch(WriteBatch.builder(Customer.class)
.mappedTableResource(table)
.addPutItemFromItem(unprocessed)
.build()));
}
}
トランザクション
再試行を伴うトランザクション書き込み
public void placeOrderWithRetry(Order order, Customer customer, int maxRetries) {
int attempt = 0;
while (attempt < maxRetries) {
try {
enhancedClient.transactWriteItems(r -> r
.addPutItem(customerTable, customer)
.addPutItem(orderTable, order));
return;
} catch (TransactionCanceledException e) {
if (e.cancellationReasons().stream()
.anyMatch(r -> r.code().equals("TransactionCanceledException")
&& r.message().contains("throughput"))) {
attempt++;
if (attempt < maxRetries) {
try { Thread.sleep((long) Math.pow(2, attempt) * 100); }
catch (InterruptedException ie) { Thread.currentThread().interrupt(); }
}
} else {
throw e; // Non-retryable error
}
}
}
}
トランザクション読み込み
TransactGetItemsEnhancedRequest request = TransactGetItemsEnhancedRequest.builder()
.addGetItem(customerTable, customerKey)
.addGetItem(orderTable, orderKey)
.build();
List<Document> results = enhancedClient.transactGetItems(request);
Spring Boot との統合
設定
@Configuration
public class DynamoDbConfiguration {
@Bean
public DynamoDbClient dynamoDbClient() {
return DynamoDbClient.builder()
.region(Region.US_EAST_1)
.build();
}
@Bean
public DynamoDbEnhancedClient dynamoDbEnhancedClient(DynamoDbClient dynamoDbClient) {
return DynamoDbEnhancedClient.builder()
.dynamoDbClient(dynamoDbClient)
.build();
}
}
リポジトリパターン
@Repository
public class CustomerRepository {
private final DynamoDbTable<Customer> customerTable;
public CustomerRepository(DynamoDbEnhancedClient enhancedClient) {
this.customerTable = enhancedClient.table("Customers", TableSchema.fromBean(Customer.class));
}
public void save(Customer customer) {
customerTable.putItem(customer);
}
public Optional<Customer> findById(String customerId) {
Key key = Key.builder().partitionValue(customerId).build();
return Optional.ofNullable(customerTable.getItem(key));
}
}
Spring Boot との統合パターンの詳細については、Spring Boot Integration Reference を参照してください。
テスト
モックを使った単体テスト
@ExtendWith(MockitoExtension.class)
class CustomerServiceTest {
@Mock
private DynamoDbClient dynamoDbClient;
@Mock
private DynamoDbEnhancedClient enhancedClient;
@Mock
private DynamoDbTable<Customer> customerTable;
@InjectMocks
private CustomerService customerService;
@Test
void saveCustomer_ShouldReturnSavedCustomer() {
// Arrange
when(enhancedClient.table(anyString(), any(TableSchema.class)))
.thenReturn(customerTable);
Customer customer = new Customer("123", "John Doe", "[email protected]");
// Act
Customer result = customerService.saveCustomer(customer);
// Assert
assertNotNull(result);
verify(customerTable).putItem(customer);
}
}
LocalStack を使った統合テスト
@Testcontainers
@SpringBootTest
class DynamoDbIntegrationTest {
@Container
static LocalStackContainer localstack = new LocalStackContainer(
DockerImageName.parse("localstack/localstack:3.0"))
.withServices(LocalStackContainer.Service.DYNAMODB);
@DynamicPropertySource
static void configureProperties(DynamicPropertyRegistry registry) {
registry.add("aws.endpoint",
() -> localstack.getEndpointOverride(LocalStackContainer.Service.DYNAMODB).toString());
}
@Autowired
private DynamoDbEnhancedClient enhancedClient;
@Test
void testCustomerCRUDOperations() {
// Test implementation
}
}
テスト戦略の詳細については、Testing Strategies を参照してください。
ベストプラクティス
- Enhanced Client を使用する:定型コードを減らし、型安全な操作を実現します
- 均等に分散するパーティションキーを設計する:ホットパーティションを避けます
- スキャンよりクエリを優先する:アクセスパターンには GSI を使用します
- 25 件または 100 件単位でバッチ処理する:BatchGetItem の上限は 100 件、BatchWriteItem の上限はテーブルごとに 25 件です
- 部分的な失敗に対処する:
ProvisionedThroughputExceededに対して、指数バックオフを伴う再試行を実装します - 条件付き書き込みを使用する:
attribute_not_exists(pk)で競合状態を防ぎます
例
CRUD リポジトリの全体例
@Repository
public class UserRepository {
private final DynamoDbTable<User> userTable;
public UserRepository(DynamoDbEnhancedClient enhancedClient) {
this.userTable = enhancedClient.table("Users", TableSchema.fromBean(User.class));
}
public User save(User user) {
userTable.putItem(user);
return user;
}
public Optional<User> findById(String userId) {
Key key = Key.builder().partitionValue(userId).build();
return Optional.ofNullable(userTable.getItem(key));
}
public void deleteById(String userId) {
userTable.deleteItem(Key.builder().partitionValue(userId).build());
}
}
再試行を伴う条件付き書き込み
public boolean createIfNotExists(User user) {
PutItemEnhancedRequest<User> request = PutItemEnhancedRequest.builder(User.class)
.item(user)
.conditionExpression("attribute_not_exists(userId)")
.build();
try {
userTable.putItemWithRequest(request);
return true;
} catch (ConditionalCheckFailedException e) {
return false; // Item already exists
}
}
制約と警告
- 項目サイズの上限:DynamoDB の項目サイズは 400KB に制限されています
- パーティションキーの設計:設計が不適切だとホットパーティションが発生します
- バッチ処理の上限:BatchGetItem は最大 100 件、BatchWriteItem はテーブルごとに最大 25 件です
- トランザクションのコスト:トランザクションでは読み込み/書き込みキャパシティユニットを 2 倍消費します
- スキャン操作:スキャンは大量の読み込みキャパシティを消費するため、必要な場合にのみ使用してください
参考資料
実装の詳細については、references フォルダーを参照してください。