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()等阻塞操作,需通过非阻塞方式从响应式流生成数组。
问题根源
- Product模型使用Flux持有关联数据:服务层已经通过
collectList()将文章列表转为List,但又包装回Flux,导致MapStruct和JSON序列化器无法正确处理Flux类型,输出了其内部属性而非实际数据。 - 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
相关产品推荐
相关产品推荐

