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

Spring WebFlux+R2DBC中如何构建响应中的子列表?

解决Spring WebFlux + R2DBC中关联子列表的非阻塞响应构建问题

问题描述

开发基于Spring WebFlux和R2DBC的测试REST API时,关联产品的文章ID列表无法正确生成预期的JSON数组。当前响应中article_ids字段返回的是Flux的内部状态对象,而非期望的ID数组。

当前响应

{
    "id": 1,
    "name": "test1",
    "description": "test description",
    "implement_cost": 200.0,
    "article_ids": {
        "scanAvailable": true,
        "prefetch": -1
    }
}

期望响应

{
    "id": 1,
    "name": "test1",
    "description": "test description",
    "implement_cost": 200.0,
    "article_ids": [
        1,
        2
    ]
}

注意:禁止使用block()/blockFirst()/blockLast()等阻塞操作,需通过非阻塞方式从响应式流生成数组。


问题根源

  1. Product模型使用Flux持有关联数据:服务层已经通过collectList()将文章列表转为List,但又包装回Flux,导致MapStruct和JSON序列化器无法正确处理Flux类型,输出了其内部属性而非实际数据。
  2. MapStruct映射逻辑错误:直接将Flux类型映射到DTO字段,序列化器无法识别Flux并转换为数组。

非阻塞解决方案

步骤1:调整Product模型,改用List持有关联数据

将Product中的Flux<ArticleEntity>改为List<ArticleEntity>,因为服务层已经获取了完整的文章列表,无需再用响应式流持有:

package ru.serov.distask.model;

import lombok.Data;
import ru.serov.distask.dao.repository.enity.ArticleEntity;
import java.util.List;

@Data
public class Product {
    private Long id;
    private String name;
    private String description;
    private Float implementCost;
    private List<ArticleEntity> articles;
}

步骤2:优化服务层逻辑,直接传递List给映射器

修改服务层的getProductById方法,去掉冗余的Flux转换,直接将收集到的文章List传入映射:

@Override
public Mono<Product> getProductById(Long id) {
    return productEntityService
            .getProductById(id)
            .flatMap(productEntity -> articleEntityService
                    .getArticlesByProductId(id)
                    .collectList()
                    .map(articles -> mapper.entityToProduct(productEntity, articles)));
}

步骤3:修改MapStruct映射器,生成ID列表

调整映射逻辑,直接从List<ArticleEntity>中提取ID并转为List<Long>,确保序列化器能直接转为JSON数组:

import java.util.List;
import java.util.stream.Collectors;
import org.mapstruct.Named;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;

@Mapper(componentModel = "spring")
public interface IProductDTOProductMapper {
    @Named("convertArticlesToIds")
    static List<Long> convertArticlesToIds(List<ArticleEntity> articles) {
        return articles.stream()
                .map(ArticleEntity::getId)
                .collect(Collectors.toList());
    }

    @Mapping(target = "implement_cost", source = "implementCost")
    @Mapping(target = "article_ids", source = "articles", qualifiedByName = "convertArticlesToIds")
    ProductDTO entityToDTO(Product src);
}

步骤4:修正控制器方法(原代码类型错误)

原控制器方法错误返回ArticleDTO,需改为返回ProductDTO,并直接使用map完成非阻塞映射:

@GetMapping("/{id}")
Mono<ProductDTO> getProductById(@PathVariable Long id) {
    return productService
            .getProductById(id)
            .map(productMapper::entityToDTO);
}

关键验证点

  • 全程无阻塞操作:所有数据库查询和数据处理均使用Reactor的非阻塞API,collectList()和map()都是响应式操作,不会阻塞线程。
  • JSON序列化正确:List<Long>会被Jackson自动序列化为JSON数组,符合期望格式。
  • 性能优化:去掉了不必要的Flux转List再转Flux的冗余操作,减少资源消耗。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 02:50:33