1. 简介
Amazon DynamoDB 是AWS提供的核心服务之一,广泛应用于构建快速、可扩展且无服务器的应用程序。它提供完全托管的NoSQL数据库解决方案,在任何规模下都能实现毫秒级性能。与传统关系型数据库不同,DynamoDB采用键值和文档数据模型,强调在设计阶段就规划好数据访问模式。
本教程将聚焦DynamoDB最强大的特性之一:使用组合主键(分区键+排序键)进行数据查询。我们将深入解析组合键模型的工作原理,并通过AWS SDK for Java演示如何高效执行这类查询。
2. 理解组合键模型
DynamoDB支持两种主键类型:简单主键(仅分区键)和组合主键(分区键+排序键)。
- 分区键决定数据的存储位置
- 排序键允许在分区内进行排序和过滤
这种模型特别适合将相关数据(如用户订单)组织在单一键下。以UserOrders
表为例:
userId
作为分区键orderDate
作为排序键
✅ 这种设计能轻松实现:
- 获取用户所有订单
- 按日期排序
- 按时间范围过滤
- 全部通过单次查询完成
3. Maven依赖
使用Java操作DynamoDB需要引入AWS SDK for Java v2,它提供现代的非阻塞API:
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>dynamodb</artifactId>
<version>2.31.26</version>
</dependency>
该依赖提供了DynamoDbClient
等核心类,用于执行表查询操作。
4. 基于分区键的查询
最简单也最常用的DynamoDB查询方式就是使用分区键。当按分区键查询时,DynamoDB会返回所有具有相同分区键值的项目。
假设UserOrders
表存储多个用户的订单,现在要获取单个用户的所有订单。由于userId
是分区键,可以这样实现:
QueryRequest queryRequest = QueryRequest.builder()
.tableName("UserOrders")
.keyConditionExpression("userId = :uid")
.expressionAttributeValues(Map.of(
":uid", AttributeValue.builder().s("user1").build()
)).build();
QueryResponse response = dynamoDbClient.query(queryRequest);
⚠️ 注意:
- 使用构建器模式构造请求,便于处理复杂场景
- 查询结果封装在
QueryResponse
对象中
获取实际数据需要调用items()
方法:
List<Map<String, AttributeValue>> items = response.items();
for (Map<String, AttributeValue> item : items) {
System.out.println("Order item: " + item.get("item").s());
}
返回的列表中每个项目都是属性名到值的映射,可进一步处理或转换为业务对象。
5. 基于分区键和排序键的查询
仅用分区键查询有时不够精确,我们可以结合排序键条件实现更精细的过滤。这允许在分区内按日期范围或特定前缀筛选结果。
例如要获取user1
在2025年1月1日之后的所有订单,由于orderDate
是排序键,可以在keyConditionExpression
中添加比较条件:
QueryRequest queryRequest = QueryRequest.builder()
.tableName("UserOrders")
.keyConditionExpression("userId = :uid AND orderDate > :startDate")
.expressionAttributeValues(Map.of(
":uid", AttributeValue.builder().s("user1").build(),
":startDate", AttributeValue.builder().s("2025-01-01").build()
)).build();
QueryResponse response = dynamoDbClient.query(queryRequest);
✅ 这种方式的优势:
- 仅扫描
user1
的分区 - 只返回
orderDate
大于2025-01-01的项目 - 避免扫描无关数据,效率极高
6. 常用排序键条件
DynamoDB支持多种排序键过滤操作符,这些操作符让我们能在分区内使用标准比较逻辑精确筛选结果。
6.1. BETWEEN操作符
使用BETWEEN
可获取特定范围内的项目,特别适合处理时间戳或日期:
QueryRequest queryRequest = QueryRequest.builder()
.tableName("UserOrders")
.keyConditionExpression("userId = :uid AND orderDate BETWEEN :from AND :to")
.expressionAttributeValues(Map.of(
":uid", AttributeValue.builder().s("user1").build(),
":from", AttributeValue.builder().s("2024-12-01").build(),
":to", AttributeValue.builder().s("2024-12-31").build()
)).build();
这将返回user1
在2024年12月的所有订单。
6.2. BEGINS_WITH操作符
当排序键是字符串(如格式化日期)时,可用BEGINS_WITH
查询特定前缀的项目,适合按年、月等分组:
QueryRequest queryRequest = QueryRequest.builder()
.tableName("UserOrders")
.keyConditionExpression("userId = :uid AND begins_with(orderDate, :prefix)")
.expressionAttributeValues(Map.of(
":uid", AttributeValue.builder().s("user1").build(),
":prefix", AttributeValue.builder().s("2025-01").build()
)).build();
这将返回user1
在2025年1月的所有订单。
7. 处理查询分页
DynamoDB限制单次查询响应最大为1MB数据。当查询结果超过此限制时,响应中会包含LastEvaluatedKey
,用于获取下一页结果。
需要通过循环处理分页:
List<Map<String, AttributeValue>> allItems = new ArrayList<>();
Map<String, AttributeValue> lastKey = null;
do {
QueryRequest.Builder requestBuilder = QueryRequest.builder()
.tableName("UserOrders")
.keyConditionExpression("userId = :uid")
.expressionAttributeValues(Map.of(
":uid", AttributeValue.fromS(userId)
));
if (lastKey != null) {
requestBuilder.exclusiveStartKey(lastKey);
}
QueryResponse response = dynamoDb.query(requestBuilder.build());
allItems.addAll(response.items());
lastKey = response.lastEvaluatedKey();
} while (lastKey != null && !lastKey.isEmpty());
return allItems;
✅ 这种模式确保:
- 获取所有匹配项目
- 不受单页数据量限制
- 特别适合大数据量分区查询或报表导出
8. 总结
DynamoDB的组合键模型为我们提供了高效组织和检索数据的强大能力。分区键与排序键的结合,使我们能够:
- 组织相关数据
- 创建高效、可扩展的访问模式
- 满足高性能应用需求
本文探讨了:
- 仅使用分区键的查询方法
- 结合排序键过滤分区内结果的增强查询
完整代码示例可在GitHub获取。