Reputation: 53
I am using spring data jdbc with MapStruct. The POJOs are aligned with the table structure with all "internal-only" data like surrogate keys, audit information etc while the Domain object is hierarchical and contains only business relevant data. I have to map between a table specific POJO (flat structure) and a Domain object with nested objects. How can I best do this with MapStruct 1.2.0 ?
// Pojo for table
public class PersonsRecord {
@Id
private Integer surrogateKey; // should not be mapped
private String loginId;
private String name;
private Timestamp memberSince;
// address columns
private Integer houseNumber;
private String addressStreet;
private String addressCity;
private String country;
// getters and setters
}
// Domain object
public class PersonDto {
private String loginId;
private String name;
private Timestamp memberSince;
private Address address;
}
@Mapper(uses = MapperUtils.class)
public interface PersonMapper {
PersonDto toPersonDto(PersonRecord person);
Address toAddress(PersonRecord person);
}
The generated code for the toPersonDto
ignores the Address
and does not call the toAddress
method like MapStruct
reference mentions. May be because both methods takes the same input type? The generated code for toAddress
looks good. Just that it is not automatically wired by MapStruct
generation.
The reverse mapping generation is similar. No automatic call.
Is there a way to make this automatic (without custom methods, or decorators etc) ? I have similar pattern in many places.
Note : The MapperUtils
is used for instantiating DTOs with immutable fields.
Update: I used the answer by Filip for mapping from PersonRecord
to PersonDto
including the Person.Address
. For the reverse mapping I did the following:
@Mapper(uses = MapperUtils.class)
public abstract class PersonMapper {
@Mapping(target = "address", source = "person")
public abstract PersonDto toPersonDto(PersonRecord person);
protected abstract Address toAddress(PersonRecord person);
public abstract PersonRecord toRecord(PersonDto dto);
public abstract void updateAddress(PersonDto dto, @MappingTarget PersonRecord person);
@AfterMapping
protected void updateAddress(PersonDto dto, @MappingTarget PersonRecord record) {
updateAddress(dto, record);
}
}
That worked without issues. Thanks Filip for providing the direction.
Upvotes: 4
Views: 5070
Reputation: 21403
The reason why the toAddress
method is not picked up is that MapStruct doesn't know that it needs to map the toPersonDto
parameter to the PersonDto.address
. You are also most likely getting a warning that the address is not mapped.
In order to do the mapping you need to tell MapStruct how to map the address
. You can do that by using @Mapping
. Your mapper will then look like
@Mapper(uses = MapperUtils.class)
public interface PersonMapper {
@Mapping(target = "address", source = "person")
PersonDto toPersonDto(PersonRecord person);
Address toAddress(PersonRecord person);
}
Update I forgot to add how to perform the reverse.
One way to perform the reverse mapping would be with custom @Mapping
annotation, you will have to do this for each entry in Address
. This is a bit cumbersome. There is an open Pull Request for MapStruct to allow this mapping to be done more easily. The PR is mapstruct/mapstruct#1686.
Another way would be to use multi source parameter mapping. This can look like:
@Mapper(uses = MapperUtils.class)
public interface PersonRecordMapper {
default PersonRecord toPersonRecord(PersonDto personDto) {
if (personDto == null) {
return null;
}
return toPersonRecord(personDto, personDto.getAddress());
}
@Mapping(target = "surrogateKey", ignore = true)
PersonRecord toPersonRecord(PersonDto personDto, Address address);
}
This is going to work without manual @Mapping
if and only if the names properties in the Address
match the ones in PersonRecord
. Otherwise you will still need to define @Mapping
.
Upvotes: 3