Java Stream:如何将多层级实体映射到单个CountryDTO?
正确实现实体到CountryDTO的映射
一、Java Stream直接实现(逻辑清晰,推荐)
你之前的代码核心错误是用flatMap把Country拆成了State流,导致每个State生成一个CountryDTO,违背了"一个国家对应一个DTO"的需求。正确做法是直接遍历每个Country,单独计算聚合数据:
List<CountryDTO> countries = countryRepository.findAll().stream() .map(country -> { // 计算当前国家所有州的人口总和 Long totalPopulation = country.getStates().stream() .mapToLong(State::getPopulation) .sum(); // 收集当前国家所有城镇并转换为TownDTO List<TownDTO> townDTOs = country.getStates().stream() .flatMap(state -> state.getTowns().stream()) .map(town -> ObjectMapperUtils.map(town, TownDTO.class)) .toList(); // 实例化DTO(假设Country实体有getId()方法,若无则去掉id参数) return new CountryDTO(country.getId(), country.getName(), totalPopulation, townDTOs); }) .toList();
关键修正点:
- 移除
flatMap,直接对单个Country做映射 - 用嵌套Stream计算人口总和(
mapToLong+sum) - 用
flatMap展开所有州的城镇列表,统一转换为TownDTO
二、ModelMapper实现(适合复杂映射场景)
如果要使用ModelMapper,需要自定义映射逻辑处理聚合字段:
1. 配置ModelMapper自定义规则
ModelMapper modelMapper = new ModelMapper(); // 配置Country到CountryDTO的映射关系 modelMapper.typeMap(Country.class, CountryDTO.class) // 自动映射id、name(字段名一致时可省略显式配置) .addMapping(Country::getId, CountryDTO::setId) .addMapping(Country::getName, CountryDTO::setName) // 自定义人口总和计算逻辑 .addMappings(mapper -> { mapper.map(source -> source.getStates().stream() .mapToLong(State::getPopulation) .sum(), CountryDTO::setPopulation); }) // 自定义城镇列表聚合+转换逻辑 .addMappings(mapper -> { mapper.map(source -> source.getStates().stream() .flatMap(state -> state.getTowns().stream()) .map(town -> modelMapper.map(town, TownDTO.class)) .toList(), CountryDTO::setTowns); });
2. 执行转换
// 单对象转换后收集为列表 List<CountryDTO> countries = countryRepository.findAll().stream() .map(country -> modelMapper.map(country, CountryDTO.class)) .toList(); // 或者直接批量转换 List<CountryDTO> countries = modelMapper.map( countryRepository.findAll(), new TypeToken<List<CountryDTO>>(){}.getType() );
内容的提问来源于stack exchange,提问作者Jack
相关产品推荐
相关产品推荐

