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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 15:36:11