Java (AWS SDK v2) での DynamoDB UpdateItem
更新そのものはビルダーの呼び出し 1 回です。Java 開発者の時間を奪うのはその周りのすべてです。決して null を返さないレスポンスオブジェクト、いちばん知りたい失敗が、おそらくあなたが捕捉した型のサブクラスになっている例外階層、そしてこの操作をそもそも表現できない高レベルのクライアント。
コード
import java.util.HashMap;
import java.util.Map;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.dynamodb.DynamoDbClient;
import software.amazon.awssdk.services.dynamodb.model.AttributeValue;
import software.amazon.awssdk.services.dynamodb.model.DynamoDbException;
import software.amazon.awssdk.services.dynamodb.model.ReturnValue;
import software.amazon.awssdk.services.dynamodb.model.UpdateItemRequest;
import software.amazon.awssdk.services.dynamodb.model.UpdateItemResponse;
public class UpdateItemExample {
public static void main(String[] args) {
try (DynamoDbClient ddb = DynamoDbClient.builder()
.region(Region.US_EAST_1)
.build()) {
Map<String, AttributeValue> key = new HashMap<>();
key.put("Artist", AttributeValue.builder().s("Arturo Sandoval").build());
key.put("SongTitle", AttributeValue.builder().s("Cubano Chant").build());
Map<String, String> names = new HashMap<>();
names.put("#upd0", "Genre");
names.put("#upd1", "Year");
names.put("#upd2", "Awards");
Map<String, AttributeValue> values = new HashMap<>();
values.put(":updValue0", AttributeValue.builder().s("Latin Jazz").build());
values.put(":updValue1", AttributeValue.builder().n("1994").build());
values.put(":updValue2", AttributeValue.builder().n("1").build());
UpdateItemRequest request = UpdateItemRequest.builder()
.tableName("Music")
.key(key)
.updateExpression("SET #upd0 = :updValue0, #upd1 = :updValue1 ADD #upd2 :updValue2")
.expressionAttributeNames(names)
.expressionAttributeValues(values)
.returnValues(ReturnValue.ALL_NEW)
.build();
UpdateItemResponse response = ddb.updateItem(request);
System.out.println(response.attributes()); // the item after the update
} catch (DynamoDbException e) {
System.err.println(e.getMessage());
}
}
}解説
AttributeValue.builder().n("1994")はStringを取ります。短い方のAttributeValue.fromN("1994")も同じです。n(int)のオーバーロードはありません。DynamoDB の数値は 38 桁の有効数字を保持しますが、Java のどのプリミティブもそうではないからです。読み戻すときもattributes().get("Awards").n()はStringです。型違いのアクセサーは例外を投げずに null を返すので、数値に対する.s()は静かな null になります。どれが設定されているかは.type()が教えてくれます。response.attributes()は決して null を返しません。ReturnValue.NONEのときは、空だが非 null のDefaultSdkAutoConstructMapを返すので、null チェックは決して発火せず、isEmpty()のチェックでも「サービスが何も送らなかった」と「アイテムに属性がない」を区別できません。その違いを知っているアクセサーが、生成されたhasAttributes()です。この SDK のコレクションメンバーにはすべて 1 つずつ用意されています。ビルダーは、肝心の部分以外のすべてを型チェックします。
updateExpression(String)は任意の文字列を受け付けます。コンパイラーはSETとタイプミスを区別できないので、式の間違いは実行時の 400 になります。ADD #upd2 :updValue2がアトミックなインクリメントで、attribute_exists(Artist)のconditionExpressionを付ければ呼び出しは更新のみになります。文法は更新式にあります。旧来の
attributeUpdatesマップより式を優先しましょう。古い例には今も出てきますが、複数種類の句も、エイリアスも、1 リクエストの中の条件も表現できません。getMessage()はサービスのメッセージではありません。SDK が独自のトランスポート情報を末尾に足します。The conditional request failed (Service: DynamoDb, Status Code: 400, Request ID: d99b117c-edd6-4dc9-8d3a-a5fa4fe9666c) (SDK Attempt Count: 1)サポートケース用にリクエスト ID が欲しいなら、これをログに出しましょう。比較は代わりに
awsErrorDetails().errorCode()で行い、素の文字列が欲しいときはawsErrorDetails().errorMessage()を使います。
ここでは catch の順序がいつも以上に重要
ConditionalCheckFailedException extends DynamoDbException なので、catch (DynamoDbException e) を先に置くと、ほぼ間違いなく分岐したかったその失敗を飲み込んでしまいます。具体的な型を先に捕まえ、ついでにアイテムも受け取りましょう。
} catch (ConditionalCheckFailedException e) {
// with .returnValuesOnConditionCheckFailure(ReturnValuesOnConditionCheckFailure.ALL_OLD)
if (e.hasItem()) {
Map<String, AttributeValue> loser = e.item(); // the item as it actually was
}
} catch (DynamoDbException e) {
// everything else
}これについては e.retryable() が false で、それが正しい挙動です。失敗した条件をリトライしても、また失敗するだけです。
覚えておくべき非対称性は、この SDK に ValidationException のクラスが存在しない ことです。dynamodb-2.35.9.jar を探しても、捕捉できるものはありません。予約語、不正な式、部分的なキー — そのすべてが、awsErrorDetails().errorCode() がたまたま ValidationException と読める、ただの DynamoDbException として届きます。静的型付け言語ではこれは面食らう欠落であり、式の間違いが実行時の文字列比較になることを意味します。
だからこそ DynamoDB の予約語は、出荷した後ではなく前に一度目を通す価値があります。リストは 573 項目に及び、Year、Name、Status を含みますが、どれも Java の Bean の中では危なそうに見えません。Bean のマッピング越しではなく生のテーブルを閲覧するには、DynoTable をダウンロードしてください。
Enhanced クライアントはこれを表現できない
データアクセスの残りが DynamoDbEnhancedClient とアノテーション付きの Bean を通っているなら、この操作こそ DynamoDbClient に引き戻される 1 つです。UpdateItemEnhancedRequest.Builder をリフレクションで覗くと、item、conditionExpression、ignoreNulls、ignoreNullsMode、returnValues、returnValuesOnConditionCheckFailure、returnConsumedCapacity、returnItemCollectionMetrics が見つかります。更新式を受け取るメソッドはありません。
実務上の帰結はアトミックカウンターです。ADD #upd2 :updValue2 は先に読み取ることなくサーバー側で Awards をインクリメントします。Enhanced クライアントが与えてくれるのは、マッピングされた Bean と、欠けたフィールドを削除するかどうかを決める ignoreNulls であって、ADD にコンパイルされるものは何もありません。Bean を介した read-modify-write は、並行実行下では更新消失のレースです。このページのスニペットが避けているのは、まさにそれです。
関連する例
- Go での DynamoDB UpdateItem — AWS SDK for Go v2 での同じ更新。
- Java での DynamoDB PutItem — 代わりにアイテムをまるごと置き換える。
- DynamoDB の更新式 —
SET、ADD、REMOVE、DELETE、そして定番の書き方。 - ReturnValues を理解する —
ReturnValuesの各オプションで何が得られるか。 - 「Attribute name is a reserved keyword」 — ここでエイリアスマップが任意ではない理由。
- 「Invalid UpdateExpression」の構文エラー — SET/ADD でよくある構文ミスの読み解き方。
参考資料
- UpdateItem — Amazon DynamoDB API Reference
- Use UpdateItem with an AWS SDK or CLI — Amazon DynamoDB Developer Guide
- DynamoDbClient — AWS SDK for Java 2.x API Reference
- UpdateItemRequest — AWS SDK for Java 2.x API Reference
- Update expressions — Amazon DynamoDB Developer Guide
最終検証日 2026-07-28、上記にリンクした公式 AWS ドキュメントに照らして確認しました。