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

Java Stream优化:非规范化实体转规范化DTO的Map实现

优化方案:用2个DTO完成冗余表到一对多结构的转换

核心思路

直接基于原FooEntity列表,通过Stream的聚合收集器先合并重复的Foo数据,同时将每行的Bar属性转换为BarDTO并归集到对应FooDTO的列表中,最后完成分组操作,全程仅使用目标的FooDTO和BarDTO两个类,无需中间过渡DTO。

定义目标DTO

// 对应一对多关系中的主实体,包含BarDTO列表
public class FooDTO {
    private Long fooId;
    private String fooName;
    private Long groupKey; // 用于最终分组的keyValue字段
    private List<BarDTO> bars = new ArrayList<>();

    // 构造器、getter、setter省略
}

// 对应一对多关系中的子实体
public class BarDTO {
    private Long barId;
    private String barContent;

    // 构造器、getter、setter省略
}

最优转换实现

方案1:先合并Foo再分组(更直观)

List<FooEntity> entityList = // 从数据库查询得到的冗余数据列表

// 第一步:按fooId聚合,合并重复Foo并生成FooDTO集合
Map<Long, FooDTO> mergedFooMap = entityList.stream()
    .collect(Collectors.toMap(
        // 用fooId作为去重key,确保同一Foo只保留一个实例
        FooEntity::getFooId,
        // 从单个Entity生成初始FooDTO和对应的BarDTO
        entity -> {
            FooDTO fooDTO = new FooDTO();
            fooDTO.setFooId(entity.getFooId());
            fooDTO.setFooName(entity.getFooName());
            fooDTO.setGroupKey(entity.getGroupKey());

            BarDTO barDTO = new BarDTO();
            barDTO.setBarId(entity.getBarId());
            barDTO.setBarContent(entity.getBarContent());
            fooDTO.getBars().add(barDTO);

            return fooDTO;
        },
        // 合并同一Foo下的多个BarDTO列表
        (existingFoo, newFoo) -> {
            existingFoo.getBars().addAll(newFoo.getBars());
            return existingFoo;
        }
    ));

// 第二步:将合并后的FooDTO按groupKey分组得到目标Map
Map<Long, List<FooDTO>> resultMap = mergedFooMap.values().stream()
    .collect(Collectors.groupingBy(FooDTO::getGroupKey));

方案2:连续Stream操作(减少变量声明)

如果希望代码更紧凑,也可以将合并和分组逻辑放在连续的Stream链中:

List<FooEntity> entityList = // 数据库查询结果

Map<Long, List<FooDTO>> resultMap = entityList.stream()
    // 先按fooId分组,聚合同一Foo的所有Entity
    .collect(Collectors.groupingBy(FooEntity::getFooId))
    .entrySet().stream()
    // 将每个分组转换为FooDTO
    .map(entry -> {
        FooEntity firstEntity = entry.getValue().get(0);
        FooDTO fooDTO = new FooDTO();
        fooDTO.setFooId(entry.getKey());
        fooDTO.setFooName(firstEntity.getFooName());
        fooDTO.setGroupKey(firstEntity.getGroupKey());

        // 转换当前Foo下的所有Bar为BarDTO列表
        List<BarDTO> barDTOs = entry.getValue().stream()
            .map(e -> {
                BarDTO bar = new BarDTO();
                bar.setBarId(e.getBarId());
                bar.setBarContent(e.getBarContent());
                return bar;
            })
            .collect(Collectors.toList());
        fooDTO.setBars(barDTOs);

        return fooDTO;
    })
    // 最终按groupKey分组
    .collect(Collectors.groupingBy(FooDTO::getGroupKey));

方案优势

  1. 仅用2个目标DTO:完全省去中间过渡DTO,贴合需求
  2. Stream操作更高效:通过聚合收集器一次性完成Foo去重和Bar归集,避免多次开闭Stream带来的开销
  3. 逻辑清晰:先处理数据冗余合并,再完成分组,符合数据转换的自然流程

注意事项

  • 确保fooId是Foo的唯一标识,否则会出现错误合并
  • 如果存在Foo无对应Bar的行(Bar字段为空),需在转换BarDTO时增加判空逻辑,避免空指针
  • 分组用的groupKey需在FooDTO中正确赋值,保证分组结果准确

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 12:47:19