You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

基于Spring Boot的DynamoDB多表关联分页实现:通过productId获取售卖对应商品的卖家分页列表

实现售卖商品的卖家分页列表方案

我来帮你梳理两种可行的实现思路,分别适配不同的数据量场景,你可以根据业务情况选择:

方案一:内存分页(适合卖家数量较少的场景)

如果售卖该商品的卖家不多,这种简单直接的方式就够用。核心是先拿全所有关联卖家,再在应用层做分页切割:

  1. 先查关联的所有sellerId
    mapper.query根据输入的productId查出所有对应的products记录,然后把里面的sellerId去重(毕竟同一个卖家可能有多条该商品的上架记录):

    DynamoDBQueryExpression<Product> queryExpr = new DynamoDBQueryExpression<Product>()
            .withHashKeyValues(new Product().setProductId(inputProductId))
            .withConsistentRead(false); // 非强一致读,节省性能,按需调整
    
    List<Product> products = dynamoDBMapper.query(Product.class, queryExpr);
    // 去重得到唯一的sellerId集合
    Set<String> uniqueSellerIds = products.stream()
            .map(Product::getSellerId)
            .collect(Collectors.toSet());
    
  2. 批量加载卖家数据
    batchLoad一次性获取所有卖家的详情:

    List<Seller> allSellers = dynamoDBMapper.batchLoad(
            uniqueSellerIds.stream()
                    .map(id -> new Seller().setSellerId(id))
                    .collect(Collectors.toList())
            ).get(Seller.class);
    
  3. 应用层做分页切割
    借助Spring的PageRequest或者手动计算分页参数(比如前端传的pageNumpageSize)来截取数据:

    int pageNum = request.getPageNum(); // 前端传入,从1开始
    int pageSize = request.getPageSize();
    int startIdx = (pageNum - 1) * pageSize;
    
    // 处理超出范围的情况
    if (startIdx >= allSellers.size()) {
        return new PageImpl<>(Collections.emptyList(), PageRequest.of(pageNum-1, pageSize), allSellers.size());
    }
    
    int endIdx = Math.min(startIdx + pageSize, allSellers.size());
    List<Seller> paginatedSellers = allSellers.subList(startIdx, endIdx);
    
    // 返回标准的分页结果,包含总条数、当前页数据等
    return new PageImpl<>(paginatedSellers, PageRequest.of(pageNum-1, pageSize), allSellers.size());
    

提醒:这种方式的短板很明显——如果卖家数量上千甚至更多,一次性加载所有数据会占用大量内存,甚至导致OOM,所以只适合小数据量场景。

方案二:DynamoDB原生分页(适合大数据量场景)

如果售卖该商品的卖家很多,建议利用DynamoDB的原生分页能力,从源头控制数据量,避免内存过载:

核心思路

products表的排序键是sellerId,所以我们可以对指定productId的products记录做分页查询,每次只拿一页的sellerId,再批量加载对应卖家,这样每次处理的数据量都是可控的。

具体步骤

  1. 分页查询products,获取当前页的sellerId
    DynamoDB的queryPage方法会返回分页结果,包含当前页数据和LastEvaluatedKey(下一页的起始标识)。API需要接收lastEvaluatedSellerId作为分页参数(替代传统的pageNum):

    DynamoDBQueryExpression<Product> queryExpr = new DynamoDBQueryExpression<Product>()
            .withHashKeyValues(new Product().setProductId(inputProductId))
            .withLimit(pageSize) // 设置每页要获取的products数量(注意会有重复sellerId,实际卖家数可能更少)
            .withConsistentRead(false);
    
    // 如果有上一页的末尾sellerId,设置为查询起始点
    if (StringUtils.isNotBlank(lastEvaluatedSellerId)) {
        queryExpr.setExclusiveStartKey(new HashMap<String, AttributeValue>() {{
            put("productId", AttributeValue.builder().s(inputProductId).build());
            put("sellerId", AttributeValue.builder().s(lastEvaluatedSellerId).build());
        }});
    }
    
    QueryResultPage<Product> productPage = dynamoDBMapper.queryPage(Product.class, queryExpr);
    List<Product> pageProducts = productPage.getResults();
    Map<String, AttributeValue> lastKey = productPage.getLastEvaluatedKey();
    // 下一页的起始sellerId,为空说明没有下一页
    String nextLastKey = lastKey != null ? lastKey.get("sellerId").getS() : null;
    
    // 去重当前页的sellerId,避免重复加载同一个卖家
    Set<String> pageSellerIds = pageProducts.stream()
            .map(Product::getSellerId)
            .collect(Collectors.toSet());
    
  2. 批量加载当前页的卖家数据

    List<Seller> pageSellers = dynamoDBMapper.batchLoad(
            pageSellerIds.stream()
                    .map(id -> new Seller().setSellerId(id))
                    .collect(Collectors.toList())
            ).get(Seller.class);
    
  3. 构造分页返回结果
    返回当前页的卖家列表,以及下一页的起始标识,方便前端发起下一页请求:

    // 自定义一个分页结果类,包含必要信息
    SellerPageResponse response = new SellerPageResponse();
    response.setSellers(pageSellers);
    response.setNextLastEvaluatedSellerId(nextLastKey);
    response.setHasNextPage(Objects.nonNull(nextLastKey));
    return response;
    

说明:这种方案的优势是内存占用极低,每次只处理一页数据,性能更稳定。需要注意的是,因为products表中可能存在同一个卖家的多条记录,所以必须对当前页的sellerId去重,避免重复查询同一个卖家的数据。

额外优化点

  • 强制去重:两种方案都必须对sellerId去重,既节省DynamoDB的读取消耗,也避免返回重复的卖家数据。
  • 一致性选择:如果业务不需要强一致性(比如允许看到几秒前的卖家数据),关闭一致读可以大幅降低读写消耗。
  • 数据排序:如果需要按卖家的某个字段排序,比如卖家评分,你可能需要给sellers表创建全局二级索引,或者在应用层对分页后的卖家数据做排序(小数据量场景)。

内容的提问来源于stack exchange,提问作者Prateek Deshpande

火山引擎 最新活动