如何使用MapStruct或Java Stream将嵌套Entity转换为DTO?
问题描述
在Spring Boot应用中,我常用Projection返回联表查询结果,现在尝试用Java Stream将嵌套实体映射为DTO,场景如下:
说明:Hibernate实体关系配置已完成,无需关注,聚焦实体到嵌套DTO的映射。
实体关系:Country包含多个State,State包含多个Town,实体类代码如下:
Country实体类
public class Country { private Long id; private String name; @OneToMany(...) private Set<State> states = new HashSet<>(); // getter/setter }
State实体类
public class State { private Long id; private Long population; @ManyToOne(...) private Country country; @OneToMany(...) private Set<Town> towns = new HashSet<>(); // getter/setter }
Town实体类
public class Town { private Long id; private String name; @ManyToOne() private State state; // getter/setter }
需求:获取包含国家名称、该国所有州的人口总和、该国所属城镇列表的国家列表,已创建DTO但不知道如何完成映射(不想多次查询数据库,当前获取的Country列表已包含所有关联数据),DTO代码如下:
@Data public class CountryResponse { private Long id; private String name; private Set<StateResponse> states; private Long population; private Set<TownResponse> towns; public CountryResponse(Country country) { this.id = country.getId(); this.name = country.getName(); // 此处不知道如何正确映射 this.states = country.getStates.stream().map(StateResponse::new) .collect(Collectors.toSet()); this.towns = this.states.stream().map(TownResponse::new) .collect(Collectors.toSet()); } }
请问该如何实现?如果MapStruct更适合,也可以使用。
解决方案
一、Java Stream手动映射
先完善子DTO StateResponse 和 TownResponse:
@Data public class StateResponse { private Long id; private Long population; public StateResponse(State state) { this.id = state.getId(); this.population = state.getPopulation(); } } @Data public class TownResponse { private Long id; private String name; public TownResponse(Town town) { this.id = town.getId(); this.name = town.getName(); } }
修改CountryResponse的构造方法,完成所有映射逻辑:
@Data public class CountryResponse { private Long id; private String name; private Set<StateResponse> states; private Long population; private Set<TownResponse> towns; public CountryResponse(Country country) { this.id = country.getId(); this.name = country.getName(); // 映射州列表 this.states = country.getStates().stream() .map(StateResponse::new) .collect(Collectors.toSet()); // 计算所有州的人口总和,处理null值避免空指针 this.population = country.getStates().stream() .map(State::getPopulation) .filter(Objects::nonNull) .reduce(0L, Long::sum); // 提取所有城镇:遍历所有州的城镇集合,再映射为TownResponse this.towns = country.getStates().stream() .flatMap(state -> state.getTowns().stream()) .map(TownResponse::new) .collect(Collectors.toSet()); } }
这样就能从已加载的Country实体中一次性完成所有数据映射,无需额外查询数据库。
二、MapStruct实现(更简洁易维护)
MapStruct适合复杂嵌套结构的实体-DTO映射,编译时自动生成映射代码,避免手动写Stream的冗余逻辑。
1. 引入Maven依赖
<dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct</artifactId> <version>1.5.5.Final</version> </dependency> <dependency> <groupId>org.mapstruct</groupId> <artifactId>mapstruct-processor</artifactId> <version>1.5.5.Final</version> <scope>provided</scope> </dependency>
2. 定义子DTO
和手动映射的StateResponse、TownResponse一致,无需编写构造方法。
3. 定义MapStruct映射接口
@Mapper(componentModel = "spring") public interface CountryMapper { CountryMapper INSTANCE = Mappers.getMapper(CountryMapper.class); // 映射Country到CountryResponse,自定义计算人口总和和提取城镇的逻辑 @Mapping(target = "population", expression = "java(calculateTotalPopulation(country))") @Mapping(target = "towns", expression = "java(extractAllTowns(country))") CountryResponse countryToCountryResponse(Country country); // 自动映射State到StateResponse StateResponse stateToStateResponse(State state); // 自动映射Town到TownResponse TownResponse townToTownResponse(Town town); // 自定义方法:计算国家总人口 default Long calculateTotalPopulation(Country country) { return country.getStates().stream() .map(State::getPopulation) .filter(Objects::nonNull) .reduce(0L, Long::sum); } // 自定义方法:提取国家下所有城镇 default Set<TownResponse> extractAllTowns(Country country) { return country.getStates().stream() .flatMap(state -> state.getTowns().stream()) .map(this::townToTownResponse) .collect(Collectors.toSet()); } }
4. 使用方式
在业务代码中直接调用映射方法:
List<Country> countries = countryRepository.findAll(); // 确保关联数据已加载(如通过fetch join或配置即时加载) List<CountryResponse> responses = countries.stream() .map(CountryMapper.INSTANCE::countryToCountryResponse) .collect(Collectors.toList());
内容的提问来源于stack exchange,提问作者Jack
相关产品推荐
相关产品推荐

