Java 中的 DynamoDB Query(AWS SDK v2)
AWS SDK for Java 2.x 里的 queryPaginator 看起来像个集合,其实不是。它是一个惰性的可重复迭代对象,而这个差别会在你第二次遍历它的时候体现在账单上。至于 Query 究竟是不是合适的读取方式,参见 Query vs. 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裹在构建器外面的 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()可用。- 在构建器里,数字是
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 vs. Scan——为什么
Query才是合理的默认选择。 - 键条件表达式——每一个合法的分区键/排序键运算符。
- "Query condition missed key schema element"——键条件点错了属性名,或者干脆跳过了分区键。
- "Query key condition not supported"——键条件用不了的运算符,比如 contains 或者第二个排序键条件。