如何使用AWS SDK for Java在Amazon DynamoDB中使用分页



根据AWS的这篇文档,可以在Amazon DynamoDB上进行分页查询,但它没有提供任何关于如何进行的示例。它只说使用迭代器方法,这是我在使用DynamoDBMapper.query()后在PaginatedQueryList上找到的。

有人能解释一下它是如何工作的,如何正确使用它,以及当你不想像扫描操作那样一次从dyneDB中检索所有结果时,它是否真的值得使用吗?

要了解Amazon DynamoDB Java API,请参阅AWS Java开发人员指南。要处理分页结果(当响应对象太大而无法在单个响应中返回时(,最佳做法是使用AWS SDK for Java V2

AWS SDK for Java 1.0中,响应包含一个必须用于检索下一页结果的令牌。AWS SDK for Java 2.x中的新增功能是进行多个服务调用以自动获得下一页结果的自动构想方法。

有关更多信息,请参阅Java V2 DEV指南中的文档主题:

使用AWS SDK for Java 2.x 检索分页结果

要查看使用DynamoDB Java V2和分页的代码示例,请参阅:

https://github.com/awsdocs/aws-doc-sdk-examples/blob/main/javav2/example_code/dynamodbasync/src/main/java/com/example/dynamodbasync/AsyncPagination.java

使用DynamoDB没有完全原生的分页方式。主要是因为DynamoDB的分布式特性——表存储在多个分区中(mb甚至存储在多台服务器中(。此外,在DynamoDB中,从查询中检索到的每个记录都有直接成本,所以我敢打赌,您不想运行类似于计数查询的东西。

但是,如果你想实现像无限滚动或";显示更多效果"有办法做到这一点。以下示例适用于AWS Java SDK v2 2.20.7。该示例显示了增强客户端的使用情况。

型号:

@Data
@DynamoDbBean
@FieldNameConstants
public class Event {
@Getter(onMethod_ = {@DynamoDbPartitionKey})
private String id;
private String name;
private String description;
@Getter(onMethod_ = {@DynamoDbSecondaryPartitionKey(indexNames = "GSI_CREATOR")})
private String creatorId;
@Getter(onMethod_ = {@DynamoDbAutoGeneratedTimestampAttribute})
private Instant updatedAt;
}

分页示例以JUnit5测试的形式提供。

使用Easy Random lib:进行数据准备

private static final TableSchema<Event> TABLE_SCHEMA = TableSchema.fromBean(Event.class);
private static final String EVENT_TABLE_NAME = "events-table";
private static final String CREATOR = UUID.randomUUID().toString();
@BeforeEach
public void initDataModel() {
final var eventFileDynamoDbTable = enhancedClient.table(EVENT_TABLE_NAME, TABLE_SCHEMA);
final var factory = new EasyRandom();
event1 = factory.nextObject(Event.class);
event1.setCreatorId(CREATOR);
eventFileDynamoDbTable.putItem(event1);
event2 = factory.nextObject(Event.class);
event2.setCreatorId(CREATOR);
eventFileDynamoDbTable.putItem(event2);
}
@AfterEach
public void cleanUp() {
final var table = enhancedClient.table(EVENT_TABLE_NAME, TABLE_SCHEMA);
table.deleteItem(event1);
table.deleteItem(event2);
}

实际分页过程:

@Test
void testSecondaryIndexSearch() {
final var table = enhancedClient.table(EVENT_TABLE_NAME, TABLE_SCHEMA);
// Use global secondary index
final var secIndex = table.index("GSI_CREATOR");
// Create a QueryConditional object that's used in the query operation.
final var queryConditional = QueryConditional
.keyEqualTo(Key.builder().partitionValue(event1.getCreatorId()).build());
// Get items in the table with limit 1
SdkIterable<Page<Event>> pages = secIndex.query(QueryEnhancedRequest.builder()
.queryConditional(queryConditional)
.limit(1)
.build());

Page<Event> next = pages.iterator().next();
assertEquals(1, next.items().size());
// retrieve last processed key. this cursor can be passed to a client. 
// so, it will be able to retrieve data from where it left off in the next query
final var cursor = next.lastEvaluatedKey();
assertNotNull(cursor);
// Get items in the table with limit 1 using cursor
// in real world case cursor will be received from the client
SdkIterable<Page<Event>> newPages = secIndex.query(QueryEnhancedRequest.builder()
.queryConditional(queryConditional)
.limit(2) // trying to fetch more then we have
.exclusiveStartKey(cursor)
.build());
Page<Event> nextWithLast = newPages.iterator().next();
assertEquals(1, nextWithLast.items().size());
final var cursorLast = nextWithLast.lastEvaluatedKey();
// there is no more data to check. so, it's the end of "pagination"
assertNull(cursorLast);
}

通知和结论:

LastEvaluatedKey基本上是记录的主键,查询操作目前已完成,并作为结果集的最后一个值返回。在这种情况下-idcreatorID

LastEvaluatedKey在两个条件下返回:

  • 查询结果已达到上限(例如,DynamoDB查询操作将数据划分为1MB大小(
  • 如果通过在查询中添加limit参数指定了限制,则它将返回一个数据集,其中还有更多记录要在下一页中进行评估

因此,可以用这种方式实现连续分页(无限滚动(。

最新更新