A. Wolf
A. Wolf

Reputation: 1347

MapStruct seems to create too many method

I'm using MapStruct v 1.4.1.Final to map Hibernate entities to corresponding DTOs. I'm using Spring Boot, so my mapper's classes have the componentModel equals to spring.

I have 2 entities, Person and Address. Person contains an Address instance:

Person

public class Person {
    private String username;
    private String name;
    private Address address;
}
public class PersonDto {
    private String id;
    private String name;
    private AddressDto address;
    private String role;
}

Address

public class Address {
    private String addr;
    private String city;
}
public class AddressDto {
    private String houseAddress;
    private String city;
}

and here are the mappers I wrote:

Mappers

@Mapper(componentModel = "spring")
public interface AddressMapper {
    @Mapping(source="person.username", target="userName")
    @Mapping(source="address.addr", target="address")
    DeliveryAddressDto personAndAddressToDeliveryAddressDto(Person person, Address address);

    @Mapping(source="person.username", target="userName")
    @Mapping(source="person.address.addr", target="address")
    DeliveryAddressDto personToDeliveryAddressDto(Person person);

    @Mapping(source = "address.addr", target = "houseAddress")
    AddressDto addressToAddressDto(Address address);
    @InheritInverseConfiguration(name = "addressToAddressDto")
    Address addressDtoToAddress(AddressDto addressDto);
}
@Mapper(componentModel = "spring", uses = {AddressMapper.class})
public interface PersonMapper {

    @Mapping(source = "id", target = "username")
    @Mapping(source = "address.houseAddress", target = "address.addr")
    @Mapping(source = "role", target = "role.id")
    Person toPerson(PersonDto personDto);
    List<Person> toPeople(List<PersonDto> personDtos);

    @InheritInverseConfiguration(name = "toPerson")
    PersonDto toPersonDto(Person person);
    List<PersonDto> toPeopleDto(List<Person> people);

    /**
     * update an existing Person with Dto info.
     * Set infos on the existing object (Person), without creating a new one.
      */
    @Mapping(source = "role", target = "role.id")
    void updatePersonFromDto(PersonDto personDto, @MappingTarget Person person);

}

where you can see that I said to the PersonMapper to use the AddressMapper to use its implementations.

However, for the conversion of the address to the address dto I found another implementation in the PersonMapper class (same for the inverse mapping):

AddressMapper implmementation

@Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    date = "2020-11-21T15:44:55+0100",
    comments = "version: 1.4.1.Final, compiler: javac, environment: Java 11 (Oracle Corporation)"
)
@Component
public class AddressMapperImpl implements AddressMapper {

    @Override
    public DeliveryAddressDto personAndAddressToDeliveryAddressDto(Person person, Address address) {
        if ( person == null && address == null ) {
            return null;
        }

        DeliveryAddressDto deliveryAddressDto = new DeliveryAddressDto();

        if ( person != null ) {
            deliveryAddressDto.setUserName( person.getUsername() );
        }
        if ( address != null ) {
            deliveryAddressDto.setAddress( address.getAddr() );
        }

        return deliveryAddressDto;
    }

    @Override
    public DeliveryAddressDto personToDeliveryAddressDto(Person person) {
        if ( person == null ) {
            return null;
        }

        DeliveryAddressDto deliveryAddressDto = new DeliveryAddressDto();

        deliveryAddressDto.setUserName( person.getUsername() );
        deliveryAddressDto.setAddress( personAddressAddr( person ) );

        return deliveryAddressDto;
    }

    @Override
    public AddressDto addressToAddressDto(Address address) {
        if ( address == null ) {
            return null;
        }

        AddressDto addressDto = new AddressDto();

        addressDto.setHouseAddress( address.getAddr() );
        addressDto.setCity( address.getCity() );

        return addressDto;
    }

    @Override
    public Address addressDtoToAddress(AddressDto addressDto) {
        if ( addressDto == null ) {
            return null;
        }

        Address address = new Address();

        address.setAddr( addressDto.getHouseAddress() );
        address.setCity( addressDto.getCity() );

        return address;
    }

    private String personAddressAddr(Person person) {
        if ( person == null ) {
            return null;
        }
        Address address = person.getAddress();
        if ( address == null ) {
            return null;
        }
        String addr = address.getAddr();
        if ( addr == null ) {
            return null;
        }
        return addr;
    }
}

PersonMapper implementation

@Generated(
    value = "org.mapstruct.ap.MappingProcessor",
    date = "2020-11-21T15:44:55+0100",
    comments = "version: 1.4.1.Final, compiler: javac, environment: Java 11 (Oracle Corporation)"
)
@Component
public class PersonMapperImpl implements PersonMapper {

    @Autowired
    private AddressMapper addressMapper;

    @Override
    public Person toPerson(PersonDto personDto) {
        if ( personDto == null ) {
            return null;
        }

        Person person = new Person();

        person.setAddress( addressDtoToAddress1( personDto.getAddress() ) );
        person.setRole( personDtoToRole( personDto ) );
        person.setUsername( personDto.getId() );
        person.setName( personDto.getName() );

        return person;
    }

    @Override
    public List<Person> toPeople(List<PersonDto> personDtos) {
        if ( personDtos == null ) {
            return null;
        }

        List<Person> list = new ArrayList<Person>( personDtos.size() );
        for ( PersonDto personDto : personDtos ) {
            list.add( toPerson( personDto ) );
        }

        return list;
    }

    @Override
    public PersonDto toPersonDto(Person person) {
        if ( person == null ) {
            return null;
        }

        PersonDto personDto = new PersonDto();

        personDto.setAddress( addressToAddressDto1( person.getAddress() ) );
        personDto.setId( person.getUsername() );
        personDto.setRole( personRoleId( person ) );
        personDto.setName( person.getName() );

        return personDto;
    }

    @Override
    public List<PersonDto> toPeopleDto(List<Person> people) {
        if ( people == null ) {
            return null;
        }

        List<PersonDto> list = new ArrayList<PersonDto>( people.size() );
        for ( Person person : people ) {
            list.add( toPersonDto( person ) );
        }

        return list;
    }

    @Override
    public void updatePersonFromDto(PersonDto personDto, Person person) {
        if ( personDto == null ) {
            return;
        }

        if ( person.getRole() == null ) {
            person.setRole( new Role() );
        }
        personDtoToRole1( personDto, person.getRole() );
        person.setName( personDto.getName() );
        person.setAddress( addressMapper.addressDtoToAddress( personDto.getAddress() ) );
    }

    protected Address addressDtoToAddress1(AddressDto addressDto) {
        if ( addressDto == null ) {
            return null;
        }

        Address address = new Address();

        address.setAddr( addressDto.getHouseAddress() );
        address.setCity( addressDto.getCity() );

        return address;
    }

    protected Role personDtoToRole(PersonDto personDto) {
        if ( personDto == null ) {
            return null;
        }

        Role role = new Role();

        role.setId( personDto.getRole() );

        return role;
    }

    protected AddressDto addressToAddressDto1(Address address) {
        if ( address == null ) {
            return null;
        }

        AddressDto addressDto = new AddressDto();

        addressDto.setHouseAddress( address.getAddr() );
        addressDto.setCity( address.getCity() );

        return addressDto;
    }

    private String personRoleId(Person person) {
        if ( person == null ) {
            return null;
        }
        Role role = person.getRole();
        if ( role == null ) {
            return null;
        }
        String id = role.getId();
        if ( id == null ) {
            return null;
        }
        return id;
    }

    protected void personDtoToRole1(PersonDto personDto, Role mappingTarget) {
        if ( personDto == null ) {
            return;
        }

        mappingTarget.setId( personDto.getRole() );
    }
}

In this implementation, why the method addressDtoToAddress1() is created? Why is not used the addressDtoToAddress() provided in the AddressMapper implementation? Those methods are identical.

Please also note that the AddressMapper is injected in the PersonMapper and the method addressDtoToAddress() of the AddressMapper is called in the method updatePersonFromDto().

Upvotes: 1

Views: 986

Answers (1)

Jaims
Jaims

Reputation: 1575

You're specifying an address mapping twice in your PersonMapper.

@Mapping(source = "id", target = "username")
@Mapping(source = "address.houseAddress", target = "address.addr")
Person toPerson(PersonDto personDto);

You're stating that PersonMapper should use the AddressMapper, but instead you add another override mapping to map the fields manually.

You can just remove the mapping and it will find the correct one by itself.

@Mapping(source = "id", target = "username")
Person toPerson(PersonDto personDto);

If you want to add an explicit mapping, you'd need to pick the source as PersonDto's address field to target Person's address field. Below will give the same result.

@Mapping(source = "id", target = "username")
@Mapping(source = "address", target = "address")
Person toPerson(PersonDto personDto);

It works fine in the updatePersonFromDto() because you didn't add an override of it and it picked it up the AddressMapper by itself. You'll notice the same behaviour if you add the same redundant mapping to it.

Upvotes: 2

Related Questions