You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Webflux 累加与详情:将同步代码转换为响应式实现

将同步库存查询代码转为WebFlux响应式实现

你需要把同步的库存详情+总计查询代码改成WebFlux响应式实现,目前已经完成了库存到响应对象的映射,但不知道如何在保留详情数据的同时计算总计。

原同步代码

@Getter
public class Stock {
    private int value;
    private String productNumber;
    public Stock(int value, String productNumber){
        this.value = value;
        this.productNumber = productNumber;
    }
}


@Getter
public class StockResponse {
    private int value;
    private String productNumber;
    public StockResponse(int value, String productNumber){
        this.value = value;
        this.productNumber = productNumber;
    }
    public StockResponse(Stock stock){
        this.value = stock.getValue();
        this.productNumber = stock.getProductNumber();
    }

    public void add(int value){
        this.value += value;
    }
    
}

public List<StockResponse> findStockResponsesDetailsAndTotal() {
    List<StockResponse> stocksResponses = new ArrayList();
    List<Stock> stocks = stockRepository.findAll();
    StockResponse total = new StockResponse(0, "Total");
    stocks.forEach(stock -> {
        total.add(stock.getValue());
        stocksResponses.add(new StockResponse(stock));
    });
    stocksResponses.add(total);
    return stocksResponses;
}

你已完成的代码

stockRepository.findAll()
  .map(StockResponse::convert)

解决方案

核心问题是reduce会把Flux转换成单个结果的Mono,导致丢失详情数据。我们需要同时保留详情流和计算总计,再把两者合并。下面提供两种实现方式:

方式1:适合数据量较小的场景(一次性收集到内存)

先把所有详情收集到Mono<List<StockResponse>>,基于这个列表计算总计,最后把详情列表转成Flux并拼接总计结果:

public Flux<StockResponse> findStockResponsesDetailsAndTotal() {
    // 收集所有库存详情到List,仅订阅一次数据库查询
    Mono<List<StockResponse>> detailListMono = stockRepository.findAll()
            .map(StockResponse::new) // 对应你写的StockResponse::convert
            .collectList();

    // 基于详情列表计算总计
    Mono<StockResponse> totalMono = detailListMono
            .map(details -> {
                int totalValue = details.stream()
                        .mapToInt(StockResponse::getValue)
                        .sum();
                return new StockResponse(totalValue, "Total");
            });

    // 先返回所有详情,再返回总计
    return detailListMono.flatMapMany(Flux::fromIterable)
            .concatWith(totalMono);
}

方式2:适合数据量较大的场景(流式处理,不加载全量数据到内存)

通过publish().autoConnect(2)让详情流被两个订阅者(详情输出和总计计算)共享,避免重复查询数据库:

public Flux<StockResponse> findStockResponsesDetailsAndTotal() {
    // 共享流,确保数据库查询仅执行一次
    Flux<StockResponse> detailResponses = stockRepository.findAll()
            .map(StockResponse::new)
            .publish()
            .autoConnect(2);

    // 基于流式数据计算总计
    Mono<StockResponse> totalResponse = detailResponses
            .reduce(new StockResponse(0, "Total"), (total, current) -> {
                total.add(current.getValue());
                return total;
            });

    // 拼接详情流和总计结果
    return Flux.concat(detailResponses, totalResponse);
}

优化建议:使用不可变对象

原StockResponse的add方法是可变操作,在多线程场景下可能存在线程安全问题。建议改成不可变实现,更符合响应式函数式编程理念:

@Getter
public class StockResponse {
    private final int value;
    private final String productNumber;

    public StockResponse(int value, String productNumber){
        this.value = value;
        this.productNumber = productNumber;
    }

    public StockResponse(Stock stock){
        this.value = stock.getValue();
        this.productNumber = stock.getProductNumber();
    }

    // 返回新对象,而非修改当前对象
    public StockResponse add(int value) {
        return new StockResponse(this.value + value, this.productNumber);
    }
}

此时总计计算的代码可以简化为纯函数式操作:

Mono<StockResponse> totalResponse = detailResponses
        .reduce(new StockResponse(0, "Total"), (total, current) -> 
                total.add(current.getValue())
        );

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 19:52:54