DynamoDB:在全局二级索引上使用 "withExclusiveStartKey" 进行分页
DynamoDB : Pagination with "withExclusiveStartKey" on a Global Secondary Index
我有一个名为 "product" 的 DynamoDB table,在 "userId" 上有一个全局二级索引。主键在 "id" 上。
我正在尝试在 "userID" GSI 上使用 "withExclusiveStartKey" 实现分页查询。
但是,当我传递有效的 lastId:
时出现以下异常
Exclusive Start Key must have same size as table's key schema
(Service: AmazonDynamoDBv2; Status Code: 400; Error Code:
ValidationException; Request ID:
822db97e-04a3-4c36-8c72-6008e2693679)
我做错了什么?
public QueryResultPage<Product> findPaged(String userId,int limit,String lastId) {
DynamoDBMapper mapper = new DynamoDBMapper(dynamoDb);
Map<String, AttributeValue> vals = new HashMap<>();
vals.put(":valUserId", new AttributeValue().withS(userId));
DynamoDBQueryExpression<Product> queryExp = new DynamoDBQueryExpression<Product>()
.withKeyConditionExpression("userId = :valUserId")
.withIndexName(ModelConsts.TBL_PRODUCT_GSI_USERID)
.withExpressionAttributeValues(vals)
.withScanIndexForward(false)
.withConsistentRead(false)
.withLimit(limit);
if (lastId != null) {//paging
Map<String, AttributeValue> exclusiveStartKey = new HashMap<String, AttributeValue>();
exclusiveStartKey.put("id", new AttributeValue().withS(lastId));
queryExp = queryExp.withExclusiveStartKey(exclusiveStartKey);
}
QueryResultPage<Product> result = mapper.queryPage(Product.class, queryExp);
return result;
}
GSI原table的键值全部设置为起始键。如果 table 有分区键和排序键,那么这两个键值都应该设置为起始键值。
在下面的例子中:-
1) videos
table 具有 videoid
作为分区键和 category
作为排序键
2) GSI 定义为 category
作为分区键,videoid
作为排序键
以下代码通过设置了起始键(即分区键和排序键)的 category
值查询 GSI。
当我不填充分区或排序键时,我可以重现您的错误。
示例代码:-
public QueryResultPage<VideoDynamoMappingAdapter> findVideosByCategoryUsingGSIAndMapperWithStartKey(
String category) {
DynamoDBMapper dynamoDBMapper = new DynamoDBMapper(dynamoDBClient);
QueryResultPage<VideoDynamoMappingAdapter> queryResult = null;
Map<String, AttributeValue> vals = new HashMap<>();
vals.put(":val1", new AttributeValue().withS(category));
DynamoDBQueryExpression<VideoDynamoMappingAdapter> queryExp = new DynamoDBQueryExpression<VideoDynamoMappingAdapter>()
.withKeyConditionExpression("category = :val1").withIndexName("VideoCategoryGsi")
.withExpressionAttributeValues(vals).withScanIndexForward(false).withConsistentRead(false).withLimit(1);
Map<String, AttributeValue> startKey = new HashMap<>();
startKey.put("videoid", new AttributeValue().withS("2"));
startKey.put("category", new AttributeValue().withS("Thriller"));
queryExp.setExclusiveStartKey(startKey);
queryResult = dynamoDBMapper.queryPage(VideoDynamoMappingAdapter.class, queryExp);
System.out.println("Result size ===>" + queryResult.getResults().size());
System.out.println("Last evaluated key ===>" + queryResult.getLastEvaluatedKey());
for (VideoDynamoMappingAdapter videoDynamoMappingAdapter : queryResult.getResults()) {
System.out.println("Video data ===>" + videoDynamoMappingAdapter.toString());
}
return queryResult;
}
我正在为那些尝试为 GSI 查询手动构建 exclusiveStartKey
的人写这个答案。看起来独占启动密钥由 3 个组件组成:
- GSI 哈希键,
- GSI 范围键和
- table键
这似乎没有在任何地方记录,因为您应该只通过调用使用返回的 lastEvaluatedKey
:
setLastEvaluatedKey(queryResult.getLastEvaluatedKey());
接受的答案是正确的,但它给 reader 留下的印象是密钥只有 2 个组件,这对我的情况没有帮助。
此处描述的解决方案在 this GitHub issue.
中首次提到
以下是使用 AWS Java SDK v2 实现上述内容的方法:
QueryRequest.Builder queryBuilder = QueryRequest.builder();
Map<String, AttributeValue> startKey = new HashMap<>(3);
// HASH/PARTITION KEY
startKey.put("gsiHashKeyAttribute", AttributeValue.builder().s(gsiHashKey).build());
// RANGE/SORT KEY
startKey.put("gsiRangeKeyAttribute", AttributeValue.builder().s(gsiRangeKey).build());
// TABLE PRIMARY KEY
startKey.put("tablePrimaryKeyAttribute", AttributeValue.builder().s(tablePrimaryKey).build());
queryBuilder.exclusiveStartKey(startKey);
当考虑索引时,最后评估的键包含两件事。
- table 键
- 索引键
您可以简单地从 QueryResultPagelast 系统输出最后计算的键 (EvaluatedKeyMap) 并获取模式。
在您的情况下,当您创建 exclusiveStartKey 时也添加最后计算的 "userId"。
exclusiveStartKey.put("userId", 最后评估的用户 id 属性值);
例如
exclusiveStartKey.put("id", new AttributeValue().withS(lastUserId));
我有一个名为 "product" 的 DynamoDB table,在 "userId" 上有一个全局二级索引。主键在 "id" 上。 我正在尝试在 "userID" GSI 上使用 "withExclusiveStartKey" 实现分页查询。 但是,当我传递有效的 lastId:
时出现以下异常Exclusive Start Key must have same size as table's key schema (Service: AmazonDynamoDBv2; Status Code: 400; Error Code: ValidationException; Request ID: 822db97e-04a3-4c36-8c72-6008e2693679)
我做错了什么?
public QueryResultPage<Product> findPaged(String userId,int limit,String lastId) {
DynamoDBMapper mapper = new DynamoDBMapper(dynamoDb);
Map<String, AttributeValue> vals = new HashMap<>();
vals.put(":valUserId", new AttributeValue().withS(userId));
DynamoDBQueryExpression<Product> queryExp = new DynamoDBQueryExpression<Product>()
.withKeyConditionExpression("userId = :valUserId")
.withIndexName(ModelConsts.TBL_PRODUCT_GSI_USERID)
.withExpressionAttributeValues(vals)
.withScanIndexForward(false)
.withConsistentRead(false)
.withLimit(limit);
if (lastId != null) {//paging
Map<String, AttributeValue> exclusiveStartKey = new HashMap<String, AttributeValue>();
exclusiveStartKey.put("id", new AttributeValue().withS(lastId));
queryExp = queryExp.withExclusiveStartKey(exclusiveStartKey);
}
QueryResultPage<Product> result = mapper.queryPage(Product.class, queryExp);
return result;
}
GSI原table的键值全部设置为起始键。如果 table 有分区键和排序键,那么这两个键值都应该设置为起始键值。
在下面的例子中:-
1) videos
table 具有 videoid
作为分区键和 category
作为排序键
2) GSI 定义为 category
作为分区键,videoid
作为排序键
以下代码通过设置了起始键(即分区键和排序键)的 category
值查询 GSI。
当我不填充分区或排序键时,我可以重现您的错误。
示例代码:-
public QueryResultPage<VideoDynamoMappingAdapter> findVideosByCategoryUsingGSIAndMapperWithStartKey(
String category) {
DynamoDBMapper dynamoDBMapper = new DynamoDBMapper(dynamoDBClient);
QueryResultPage<VideoDynamoMappingAdapter> queryResult = null;
Map<String, AttributeValue> vals = new HashMap<>();
vals.put(":val1", new AttributeValue().withS(category));
DynamoDBQueryExpression<VideoDynamoMappingAdapter> queryExp = new DynamoDBQueryExpression<VideoDynamoMappingAdapter>()
.withKeyConditionExpression("category = :val1").withIndexName("VideoCategoryGsi")
.withExpressionAttributeValues(vals).withScanIndexForward(false).withConsistentRead(false).withLimit(1);
Map<String, AttributeValue> startKey = new HashMap<>();
startKey.put("videoid", new AttributeValue().withS("2"));
startKey.put("category", new AttributeValue().withS("Thriller"));
queryExp.setExclusiveStartKey(startKey);
queryResult = dynamoDBMapper.queryPage(VideoDynamoMappingAdapter.class, queryExp);
System.out.println("Result size ===>" + queryResult.getResults().size());
System.out.println("Last evaluated key ===>" + queryResult.getLastEvaluatedKey());
for (VideoDynamoMappingAdapter videoDynamoMappingAdapter : queryResult.getResults()) {
System.out.println("Video data ===>" + videoDynamoMappingAdapter.toString());
}
return queryResult;
}
我正在为那些尝试为 GSI 查询手动构建 exclusiveStartKey
的人写这个答案。看起来独占启动密钥由 3 个组件组成:
- GSI 哈希键,
- GSI 范围键和
- table键
这似乎没有在任何地方记录,因为您应该只通过调用使用返回的 lastEvaluatedKey
:
setLastEvaluatedKey(queryResult.getLastEvaluatedKey());
接受的答案是正确的,但它给 reader 留下的印象是密钥只有 2 个组件,这对我的情况没有帮助。 此处描述的解决方案在 this GitHub issue.
中首次提到以下是使用 AWS Java SDK v2 实现上述内容的方法:
QueryRequest.Builder queryBuilder = QueryRequest.builder();
Map<String, AttributeValue> startKey = new HashMap<>(3);
// HASH/PARTITION KEY
startKey.put("gsiHashKeyAttribute", AttributeValue.builder().s(gsiHashKey).build());
// RANGE/SORT KEY
startKey.put("gsiRangeKeyAttribute", AttributeValue.builder().s(gsiRangeKey).build());
// TABLE PRIMARY KEY
startKey.put("tablePrimaryKeyAttribute", AttributeValue.builder().s(tablePrimaryKey).build());
queryBuilder.exclusiveStartKey(startKey);
当考虑索引时,最后评估的键包含两件事。
- table 键
- 索引键
您可以简单地从 QueryResultPagelast 系统输出最后计算的键 (EvaluatedKeyMap) 并获取模式。
在您的情况下,当您创建 exclusiveStartKey 时也添加最后计算的 "userId"。 exclusiveStartKey.put("userId", 最后评估的用户 id 属性值);
例如
exclusiveStartKey.put("id", new AttributeValue().withS(lastUserId));