如何在ModelMapper中正确定义转换器解决映射配置异常
问题描述
我有两个JPA实体:Person和Address,以及三个DTO:PersonDto、AddressDto和BasicAddressDto,其中AddressDto继承自BasicAddressDto。PersonDto的address属性类型为BasicAddressDto,但我希望在将Person映射到PersonDto时,强制ModelMapper使用AddressDto实例而非BasicAddressDto。
我编写了如下转换器:
Converter<Address, BasicAddressDto> CONVERTER = new AbstractConverter<Address, BasicAddressDto>() { @Override protected BasicAddressDto convert(Address address) { return new AddressDto(address.getUuid(), address.getStreet(), address.getCity()); } };
并进行了ModelMapper配置:
ModelMapper modelMapper = new ModelMapper(); modelMapper.typeMap(Person.class, PersonDto.class).addMappings(mapper -> mapper.using(CONVERTER) .map(Person::getAddress, PersonDto::setAddress));
但出现了如下错误:
org.modelmapper.ConfigurationException: ModelMapper configuration errors: 1) Not able to skip address., because there are already nested properties are mapped: [address.uuid.]. Do you skip the property after the implicit mappings mapped? We recommended you to create an empty type map, and followed by addMappings and implicitMappings calls
修复方案
这个错误的核心原因是ModelMapper默认会自动隐式映射嵌套属性(比如address.uuid),而后续手动配置整个address属性的转换器时,和已生成的隐式映射规则冲突。提供两种可行的修复方式:
方式一:创建空TypeMap避免隐式映射冲突
先创建不包含自动隐式映射的空TypeMap,再添加自定义的address映射规则,最后按需启用其他属性的隐式映射:
ModelMapper modelMapper = new ModelMapper(); // 创建空TypeMap,禁用自动隐式映射 TypeMap<Person, PersonDto> typeMap = modelMapper.createTypeMap(Person.class, PersonDto.class); // 添加address属性的自定义映射逻辑 typeMap.addMappings(mapper -> mapper.using(CONVERTER) .map(Person::getAddress, PersonDto::setAddress)); // 启用其他属性(如uuid、name)的自动隐式映射 typeMap.implicitMappings();
方式二:注册全局转换器(更简洁)
直接将转换器注册为全局规则,ModelMapper在遇到Address转BasicAddressDto的场景时,会自动使用该转换器返回AddressDto实例,无需单独配置Person的映射:
ModelMapper modelMapper = new ModelMapper(); // 注册全局转换器 modelMapper.addConverter(CONVERTER);
完整测试代码示例
import org.junit.jupiter.api.Test; import org.modelmapper.AbstractConverter; import org.modelmapper.Converter; import org.modelmapper.ModelMapper; import org.modelmapper.TypeMap; import java.util.UUID; import static org.assertj.core.api.Assertions.assertThat; public class ModelMapperMappingTest { @Test void testWithEmptyTypeMap() { Converter<Address, BasicAddressDto> CONVERTER = new AbstractConverter<Address, BasicAddressDto>() { @Override protected BasicAddressDto convert(Address address) { return new AddressDto(address.getUuid(), address.getStreet(), address.getCity()); } }; ModelMapper modelMapper = new ModelMapper(); TypeMap<Person, PersonDto> typeMap = modelMapper.createTypeMap(Person.class, PersonDto.class); typeMap.addMappings(mapper -> mapper.using(CONVERTER) .map(Person::getAddress, PersonDto::setAddress)); typeMap.implicitMappings(); Person person = new Person( UUID.randomUUID(), "John Doe", new Address(UUID.randomUUID(), "Palm Ave", "Gold Potato City")); PersonDto personDto = modelMapper.map(person, PersonDto.class); assertThat(personDto.getAddress()).isInstanceOf(AddressDto.class); assertThat(((AddressDto) personDto.getAddress()).getStreet()).isEqualTo("Palm Ave"); } @Test void testWithGlobalConverter() { Converter<Address, BasicAddressDto> CONVERTER = new AbstractConverter<Address, BasicAddressDto>() { @Override protected BasicAddressDto convert(Address address) { return new AddressDto(address.getUuid(), address.getStreet(), address.getCity()); } }; ModelMapper modelMapper = new ModelMapper(); modelMapper.addConverter(CONVERTER); Person person = new Person( UUID.randomUUID(), "John Doe", new Address(UUID.randomUUID(), "Palm Ave", "Gold Potato City")); PersonDto personDto = modelMapper.map(person, PersonDto.class); assertThat(personDto.getAddress()).isInstanceOf(AddressDto.class); assertThat(((AddressDto) personDto.getAddress()).getCity()).isEqualTo("Gold Potato City"); } // JPA实体类 @Data @AllArgsConstructor class Person { private UUID uuid; private String name; private Address address; } @Data @AllArgsConstructor class Address { private UUID uuid; private String street; private String city; } // DTO类 @Getter @Setter class PersonDto { private UUID uuid; private String name; private BasicAddressDto address; } @Getter @Setter class AddressDto extends BasicAddressDto { private String street; private String city; public AddressDto(UUID uuid, String street, String city) { super(uuid); this.street = street; this.city = city; } } @Getter @Setter @AllArgsConstructor class BasicAddressDto { private UUID uuid; } }
内容的提问来源于stack exchange,提问作者CodeGrinder
相关产品推荐
相关产品推荐

