如何在Quarkus应用中使用DynamoDBMapper实现手动分页?
在Quarkus中用DynamoDBMapper实现手动分页
刚好我之前在Quarkus项目里用DynamoDBMapper做过分页,给你梳理下具体的实现思路和代码示例,亲测好用~
DynamoDB的分页核心依赖LastEvaluatedKey(游标),它标记了当前查询的最后一条数据的主键,下一页查询时用这个键作为起始点,就能避免传统页码分页的重复/遗漏问题(毕竟DynamoDB是分布式数据库,数据可能随时变动)。结合DynamoDBMapper的对象映射能力,实现起来很顺畅。
步骤1:定义分页结果类
首先我们需要一个统一的分页返回结构,既要包含当前页的数据列表,也要返回下一页的游标(方便前端传递):
import com.amazonaws.services.dynamodbv2.model.AttributeValue; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.IOException; import java.util.List; import java.util.Map; public class PagedResult<T> { private List<T> items; private String nextCursor; // 序列化后的LastEvaluatedKey,用于下一页查询 // 构造函数:自动处理游标序列化 public PagedResult(List<T> items, Map<String, AttributeValue> lastEvaluatedKey, ObjectMapper objectMapper) throws IOException { this.items = items; if (lastEvaluatedKey != null && !lastEvaluatedKey.isEmpty()) { // 将DynamoDB的键对象序列化为JSON字符串,方便前端存储传递 this.nextCursor = objectMapper.writeValueAsString(lastEvaluatedKey); } else { this.nextCursor = null; // 没有下一页时返回null } } // 静态方法:将前端传递的游标字符串反序列化为DynamoDB的键对象 public static Map<String, AttributeValue> deserializeCursor(String cursorStr, ObjectMapper objectMapper) throws IOException { if (cursorStr == null || cursorStr.isBlank()) { return null; } return objectMapper.readValue(cursorStr, new TypeReference<Map<String, AttributeValue>>() {}); } // Getter和Setter省略,按需生成 public List<T> getItems() { return items; } public void setItems(List<T> items) { this.items = items; } public String getNextCursor() { return nextCursor; } public void setNextCursor(String nextCursor) { this.nextCursor = nextCursor; } }
步骤2:编写带分页的查询方法
接下来在你的业务服务类里,实现基于DynamoDBMapper的分页查询逻辑。这里以查询某个分类下的产品为例:
import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBMapper; import com.amazonaws.services.dynamodbv2.datamodeling.DynamoDBQueryExpression; import com.amazonaws.services.dynamodbv2.model.AttributeValue; import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; import java.io.IOException; import java.util.List; @ApplicationScoped public class ProductService { @Inject DynamoDBMapper dynamoDBMapper; // Quarkus自动注入配置好的DynamoDBMapper @Inject ObjectMapper objectMapper; // Quarkus自带的Jackson ObjectMapper /** * 查询指定分类的产品,支持分页 * @param category 产品分类(假设是DynamoDB表的哈希键) * @param pageSize 每页条数(注意:DynamoDB单请求最多返回1MB数据,不要设置过大) * @param nextCursor 上一页返回的游标(首次查询传null) * @return 分页结果 */ public PagedResult<Product> getProductsByCategory(String category, int pageSize, String nextCursor) throws IOException { // 1. 构建查询的哈希键条件 Product hashKeyExample = new Product(); hashKeyExample.setCategory(category); // 假设category是表的哈希键 // 2. 构建QueryExpression,设置每页条数和起始游标 DynamoDBQueryExpression<Product> queryExpr = new DynamoDBQueryExpression<Product>() .withHashKeyValues(hashKeyExample) .withLimit(pageSize); // 设置每页返回的最大条数 // 如果有上一页的游标,设置为查询的起始点 if (nextCursor != null) { Map<String, AttributeValue> startKey = PagedResult.deserializeCursor(nextCursor, objectMapper); queryExpr.withExclusiveStartKey(startKey); } // 3. 执行查询,得到分页结果列表(PaginatedQueryList是懒加载的) var paginatedResults = dynamoDBMapper.query(Product.class, queryExpr); // 4. 加载当前页的所有数据(避免懒加载带来的后续请求) List<Product> currentPageItems = paginatedResults.loadAll(); // 5. 获取下一页的游标(如果为null,说明没有更多数据) Map<String, AttributeValue> lastEvaluatedKey = paginatedResults.getLastEvaluatedKey(); // 6. 封装成分页结果返回 return new PagedResult<>(currentPageItems, lastEvaluatedKey, objectMapper); } }
关键注意事项
- 不要用传统页码分页:DynamoDB是分布式数据库,数据可能在两次查询之间插入/删除,页码分页会导致数据重复或遗漏,游标式分页是官方推荐的最佳实践。
- pageSize限制:DynamoDB单请求最多返回1MB的数据,所以pageSize不要设置过大,建议根据你的单条数据大小调整(比如单条1KB的话,pageSize设为1000就接近1MB了)。
- 排序支持:如果需要按某个字段排序,可以在
DynamoDBQueryExpression中添加withScanIndexForward(false)(倒序)或者withRangeKeyCondition来指定排序条件(需要表有对应的全局二级索引或本地二级索引)。 - Quarkus配置:确保你已经添加了
quarkus-amazon-dynamodb依赖,并在application.properties中配置了AWS相关参数:quarkus.amazon.dynamodb.access-key=你的AWS访问密钥 quarkus.amazon.dynamodb.secret-key=你的AWS秘密密钥 quarkus.amazon.dynamodb.region=你的AWS区域(比如us-east-1)
这样实现后,前端只需要在首次查询时传pageSize和nextCursor=null,后续每次请求带上上一页返回的nextCursor,直到nextCursor为null就说明没有更多数据了。
内容的提问来源于stack exchange,提问作者user19573093
相关产品推荐
相关产品推荐

