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));
方案优势
- 仅用2个目标DTO:完全省去中间过渡DTO,贴合需求
- Stream操作更高效:通过聚合收集器一次性完成Foo去重和Bar归集,避免多次开闭Stream带来的开销
- 逻辑清晰:先处理数据冗余合并,再完成分组,符合数据转换的自然流程
注意事项
- 确保
fooId是Foo的唯一标识,否则会出现错误合并 - 如果存在Foo无对应Bar的行(Bar字段为空),需在转换
BarDTO时增加判空逻辑,避免空指针 - 分组用的
groupKey需在FooDTO中正确赋值,保证分组结果准确
内容的提问来源于stack exchange,提问作者martinz
相关产品推荐
相关产品推荐

