如何实现DynamoDB分页Scan并将扫描参数存入新表?
解决方案
首先,你的代码里withFilterExpression(status)的用法有误,DynamoDB的FilterExpression需要使用占位符来避免语法错误和注入风险,正确做法是结合withExpressionAttributeNames和withExpressionAttributeValues定义。
接下来,要实现将扫描上下文(Limit、FilterExpression、LastEvaluatedKey)存入另一个DynamoDB表,你可以新增一个方法处理PutItem操作,每次扫描完成后调用该方法保存当前扫描状态。
修改后的完整代码
package dev.example; import com.amazonaws.services.dynamodbv2.AmazonDynamoDB; import com.amazonaws.services.dynamodbv2.AmazonDynamoDBClientBuilder; import com.amazonaws.services.dynamodbv2.model.AttributeValue; import com.amazonaws.services.dynamodbv2.model.PutItemRequest; import com.amazonaws.services.dynamodbv2.model.ScanRequest; import com.amazonaws.services.dynamodbv2.model.ScanResult; import java.util.HashMap; import java.util.Map; public class ScanTable { static AmazonDynamoDB client = AmazonDynamoDBClientBuilder.standard().build(); // 存储扫描上下文的表名 private static final String SCAN_CONTEXT_TABLE = "ScanContextTable"; public static void scan() { Map<String, AttributeValue> lastKeyEvaluated = null; String filterExpression = "#status = :openStatus"; int limit = 10; do { // 构建正确的ScanRequest,修复FilterExpression用法 ScanRequest scanRequest = new ScanRequest() .withTableName("tablename") .withLimit(limit) .withExclusiveStartKey(lastKeyEvaluated) .withFilterExpression(filterExpression) .withExpressionAttributeNames(Map.of("#status", "status")) .withExpressionAttributeValues(Map.of(":openStatus", new AttributeValue().withS("open"))); ScanResult result = client.scan(scanRequest); // 处理扫描到的条目 for (Map<String, AttributeValue> item : result.getItems()) { System.out.println(item); } // 获取本次扫描返回的LastEvaluatedKey Map<String, AttributeValue> currentLastEvaluatedKey = result.getLastEvaluatedKey(); // 保存扫描上下文到另一个表 saveScanContext(limit, filterExpression, lastKeyEvaluated, currentLastEvaluatedKey); // 更新lastKeyEvaluated用于下一次扫描 lastKeyEvaluated = currentLastEvaluatedKey; } while (lastKeyEvaluated != null); // 修正循环条件:还有未扫描数据时继续 } /** * 将扫描上下文存入指定DynamoDB表 * @param limit 本次扫描使用的Limit * @param filterExpression 本次扫描使用的FilterExpression * @param exclusiveStartKey 本次扫描使用的ExclusiveStartKey(即上一次的LastEvaluatedKey) * @param lastEvaluatedKey 本次扫描返回的LastEvaluatedKey */ private static void saveScanContext(int limit, String filterExpression, Map<String, AttributeValue> exclusiveStartKey, Map<String, AttributeValue> lastEvaluatedKey) { // 构造要存入的条目 Map<String, AttributeValue> item = new HashMap<>(); // 用UUID作为主键,确保条目唯一 item.put("scanId", new AttributeValue().withS(java.util.UUID.randomUUID().toString())); item.put("limit", new AttributeValue().withN(String.valueOf(limit))); item.put("filterExpression", new AttributeValue().withS(filterExpression)); // 存入ExclusiveStartKey(第一次扫描时为null) if (exclusiveStartKey != null) { item.put("exclusiveStartKey", new AttributeValue().withM(exclusiveStartKey)); } else { item.put("exclusiveStartKey", new AttributeValue().withNULL(true)); } // 存入本次返回的LastEvaluatedKey(扫描结束时为null) if (lastEvaluatedKey != null) { item.put("lastEvaluatedKey", new AttributeValue().withM(lastEvaluatedKey)); } else { item.put("lastEvaluatedKey", new AttributeValue().withNULL(true)); } // 执行PutItem操作 PutItemRequest putItemRequest = new PutItemRequest() .withTableName(SCAN_CONTEXT_TABLE) .withItem(item); client.putItem(putItemRequest); } public static void main(String[] args) throws Exception { scan(); } }
关键说明
- FilterExpression修复:原代码直接传入"open"会导致语法错误,改用占位符绑定属性名和值,符合DynamoDB的表达式规范。
- 循环条件修正:原代码
while (lastKeyEvaluated == null)会提前终止扫描,正确条件为lastKeyEvaluated != null,表示还有未扫描数据时继续。 - 扫描上下文存储:
- 用UUID作为
scanId主键,保证每个上下文条目唯一; - Limit以字符串类型存入(DynamoDB数字类型用
withN); - LastEvaluatedKey是
Map<String, AttributeValue>类型,直接用withM存入DynamoDB的Map属性; - 处理null场景:第一次扫描的
exclusiveStartKey、扫描结束的lastEvaluatedKey均标记为NULL类型。
- 用UUID作为
- 表结构要求:存储上下文的表
ScanContextTable需设置scanId(字符串类型)为主键,其他属性为非主键可选属性。
内容的提问来源于stack exchange,提问作者Panda1997
相关产品推荐
相关产品推荐

