Java での DynamoDB Query(AWS SDK v2)
AWS SDK for Java 2.x の queryPaginator はコレクションのように見えますが、コレクションではありません。遅延評価される再反復可能オブジェクトであり、その違いは 2 回目にループしたときに請求書に現れます。そもそも Query が正しい読み取りなのかどうかは Query と Scan の比較を参照してください。
コード
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
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.QueryRequest;
import software.amazon.awssdk.services.dynamodb.model.QueryResponse;
public class QueryExample {
public static void main(String[] args) {
try (DynamoDbClient ddb = DynamoDbClient.builder()
.region(Region.US_EAST_1)
.build()) {
Map<String, String> names = new HashMap<>();
names.put("#hashKey", "Artist");
names.put("#rangeKey", "SongTitle");
Map<String, AttributeValue> values = new HashMap<>();
values.put(":hashKeyValue", AttributeValue.builder().s("Arturo Sandoval").build());
values.put(":rangeKeyValue", AttributeValue.builder().s("C").build());
QueryRequest request = QueryRequest.builder()
.tableName("Music")
.keyConditionExpression(
"#hashKey = :hashKeyValue AND begins_with(#rangeKey, :rangeKeyValue)")
.expressionAttributeNames(names)
.expressionAttributeValues(values)
.build();
List<Map<String, AttributeValue>> items = new ArrayList<>();
for (QueryResponse page : ddb.queryPaginator(request)) {
items.addAll(page.items());
}
System.out.println("Found " + items.size() + " items");
} catch (DynamoDbException e) {
System.err.println(e.getMessage());
}
}
}QueryIterable は反復のたびにクエリを実行し直します
1 曲あたり約 3.9 KB、すべてが Artist = "Arturo Sandoval" の下にある 600 曲のフィクスチャに対して、ddb.queryPaginator(request) は 2.6 ms で返り、何も送信しませんでした。そのうえで、同じ QueryIterable オブジェクトを 2 回ループしました。
queryPaginator(request) returned in 2.60 ms (class QueryIterable, 0 requests)
iteration 1: pages=3 items=600 capacity=284.5
iteration 2: pages=3 items=600 capacity=284.5 <- same object1 つの変数に対する 2 つの for ループで、569 読み取りユニットです。ページはキャッシュされていません。反復のたびに LastEvaluatedKey を最初からたどり直します。アイテムが 2 回必要なら、例のように 1 度だけ List に汲み出してください。
同じ遅延評価の裏返しが、エラーが表面化する場所です。パーティションキーを欠いたキー条件のリクエストを組み立てても、まだ呼び出しが起きていないため queryPaginator は何も言わずに受け取ります。
queryPaginator(bad) constructed without throwing
threw on iteration: DynamoDbException / ValidationException /
Query condition missed key schema element / http 400ビルダーを囲んだ try-catch は何も捕まえません。囲むべきはループのほうで、例がブロック全体を 1 つの try に入れているのはそのためです。
2026-07-28 に、OpenJDK 26.0.1 上の software.amazon.awssdk:dynamodb 2.49.4 で、DynamoDB Local(amazon/dynamodb-local)に対して計測しました。
解説
ddb.queryPaginator(request).items()はページを平坦化してIterable<Map<String, AttributeValue>>にし、裏でページ送りするので、アイテムだけが欲しいときは例の二重ループが 1 つになります。SdkIterableでもあるので.stream()も使えます。- ビルダーでは数値は
Stringです。AttributeValue.builder().n("1994")は.n(1994)のタイプミスではありません。n()のセッターはjava.lang.Stringを取ります。DynamoDB がバイナリ浮動小数点の丸めを避けるために数値を 10 進テキストで運ぶからです。Java のintを渡してもコンパイルできません。 keyConditionExpressionはパーティションキーの等価条件に加えて、ソートキーの条件を最大 1 つ取ります(=、<、<=、>、>=、BETWEEN、begins_with)。.scanIndexForward(false)で順序が逆になり、.indexName("...")でセカンダリインデックスに向けられます。- もう一方の使い勝手が Enhanced Client です。
software.amazon.awssdk.enhanced.dynamodbはMap<String, AttributeValue>ではなくアノテーション付きの Bean をマッピングし、そのqueryは上で計測したのと同じ遅延再反復の挙動を持つPageIterable<T>を返します。
ビジュアルに行う
この例の #hashKey も #rangeKey も、必須ではない別名です。Artist も SongTitle も AWS の 573 語の予約語リストには載っていません。予約語チェッカーは、自分の属性名のどれが本当に # 扱いを必要とするかを教えてくれるので、別名マップがカーゴカルトでなくなります。
QueryRequest を組み立てる前に、実際のテーブルに対してキー条件を試すには、DynoTable をダウンロードして結果をグリッドでページ送りしてください。
関連する例
- Go での DynamoDB Query — AWS SDK for Go v2 による同じクエリ。
- Java での DynamoDB Scan — パーティションにキーで入れないとき。
- Query と Scan の比較 —
Queryが既定として正しい理由。 - キー条件式 — 使えるパーティション/ソートキー演算子のすべて。
- 「Query condition missed key schema element」 — キー条件が誤った属性を指しているか、パーティションキーを飛ばしている。
- 「Query key condition not supported」 — contains や 2 つ目のソートキー条件のように、キー条件では使えない演算子。