Java 中的 DynamoDB Query(AWS SDK v2)
AWS SDK for Java 2.x 裡的 queryPaginator 看起來像一個集合,但它不是。它是一個惰性、可重複迭代的物件,而這個差別會在你第二次迴圈它時顯示在你的帳單上。至於 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 每次迭代都會重跑一次查詢
針對一個 600 首歌的樣本資料、每首歌約 3.9 KB 且全都在 Artist = "Arturo Sandoval" 之下,ddb.queryPaginator(request) 在 2.6 毫秒內回傳,而且什麼都沒送出。接著同一個 QueryIterable 物件被迴圈了兩次:
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 object在一個變數上跑兩個 for 迴圈,569 個讀取單位。那些頁面從來沒有被快取;每次迭代都會從頭再走一遍 LastEvaluatedKey。如果你需要那些項目兩次,就像範例那樣,把可迭代物件一次性倒進一個 List 裡。
同一種惰性的另一面,是錯誤浮現的位置。建構一個索引鍵條件漏掉分割區索引鍵的請求,queryPaginator 會毫無怨言地接受,因為還沒有任何呼叫發生:
queryPaginator(bad) constructed without throwing
threw on iteration: DynamoDbException / ValidationException /
Query condition missed key schema element / http 400包在 builder 外面的 try-catch 什麼也捕捉不到。它必須包住那個迴圈,這也是為什麼範例把整段程式碼放進同一個 try 裡。
已於 2026-07-28 對照 9000 埠上的 DynamoDB Local(amazon/dynamodb-local),使用 OpenJDK 26.0.1 上的 software.amazon.awssdk:dynamodb 2.49.4 實測。
說明
ddb.queryPaginator(request).items()會把那些頁面攤平成一個Iterable<Map<String, AttributeValue>>並在你背後分頁,所以當你只想要項目時,範例裡的兩層迴圈就縮成一層。它同時也是一個SdkIterable,所以.stream()可用。- 在 builder 裡數字是
String。AttributeValue.builder().n("1994")不是.n(1994)的打字錯誤 —n()setter 收的是java.lang.String,因為 DynamoDB 是以十進位文字傳輸數字,以避免二進位浮點的捨入。傳一個 Javaint進去根本編譯不過。 keyConditionExpression接受一個分割區索引鍵上的等式,加上最多一個排序索引鍵條件(=、<、<=、>、>=、BETWEEN、begins_with);.scanIndexForward(false)會反轉順序,而.indexName("...")會改指向某個次要索引。- Enhanced Client 是另一種使用手感。
software.amazon.awssdk.enhanced.dynamodb對應的是加了註解的 bean,而不是Map<String, AttributeValue>,而它的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 或第二個排序索引鍵條件。