Igor
Igor

Reputation: 658

Mapstruct: how to map multiple fields from DTO to an object in Entity?

i have this DTO:

@NoArgsConstructor
public class DataDTO implements DTO {

    private static final long serialVersionUID = -5105904799152965475L;

    private Long deviceId;

    private OffsetDateTime generatedOn;

    public Long getDeviceId() {
        return deviceId;
    }

    public void setDeviceId(Long deviceId) {
        this.deviceId = deviceId;
    }

    public OffsetDateTime getGeneratedOn() {
        return generatedOn;
    }

    public void setGeneratedOn(OffsetDateTime generatedOn) {
        this.generatedOn = generatedOn;
    }
}

i have this MongoDB document:

@Document(collection = "data")
@EqualsAndHashCode
public class DataDocument {

    private static final long serialVersionUID = 1772572723546311500L;

    @Id
    private IdByDeviceIdAndGeneratedOn id;

    public DataDocument() {
    }

    public IdByDeviceIdAndGeneratedOn getId() {
        return id;
    }

    public void setId(IdByDeviceIdAndGeneratedOn id) {
        this.id = id;
    }
}

and this is the @Id class for MongoDB Document:

@EqualsAndHashCode
@ToString
public class IdByDeviceIdAndGeneratedOn {

    @Id
    private final Long deviceId;

    @Id
    @Field("generated_on")
    @DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
    private final OffsetDateTime generatedOn;

    public IdByDeviceIdAndGeneratedOn(final Long deviceId, final OffsetDateTime         generatedOn) {
        this.deviceId = Objects.requireNonNull(deviceId);
        this.generatedOn = Objects.requireNonNull(generatedOn);
    }

    public Long getDeviceId() {
        return deviceId;
    }

    public OffsetDateTime getGeneratedOn() {
        return generatedOn;
    }
}

this is the mapper for this Key class:

@Mapper(unmappedTargetPolicy = ReportingPolicy.ERROR, componentModel = "spring")
public interface IdByDeviceIdAndGeneratedOnMapper {

    default IdByDeviceIdAndGeneratedOn toId(final Long deviceId, final   OffsetDateTime generatedOn) {
        return new IdByDeviceIdAndGeneratedOn(deviceId, generatedOn);
    }
    default Long getDeviceId(final IdByDeviceIdAndGeneratedOn id) {
        return id.getDeviceId();
    }

    default OffsetDateTime getGeneratedOn(final IdByDeviceIdAndGeneratedOn id) {
        return id.getGeneratedOn();
    }

and this is the @Mapper for DataDTO and DataDocument:

@Mapper( unmappedTargetPolicy = ReportingPolicy.ERROR,
    uses = {IdByDeviceIdAndGeneratedOnMapper.class,
            AccelerometerDocumentMapper.class,
            GpsDocumentMapper.class,
            GsmDocumentMapper.class
})
public interface DataDocumentMapper extends DocumentMapper<DataDTO, DataDocument> {

}

this is the generic mapper:

/**
 * Contract for a generic dto to entity mapper.
 *
 * @param <DTO> - DTO source type parameter.
 * @param <DOCUMENT> - MongoDB Document destination type parameter.
 */

public interface DocumentMapper<DTO, DOCUMENT> {

DOCUMENT toDocument(DTO dto);

DTO toDto(DOCUMENT document);

}

Currently i'm receiving this errors: for MongoDB Data docment:

Unmapped target property: "id".

for DTO:

Unmapped target properties: "deviceId, generatedOn".

How to solve this errors without loosing immutability of Id class?

Upvotes: 1

Views: 6745

Answers (2)

Filip
Filip

Reputation: 21393

What you are trying to do is to use (using constructors to construct objects) is not yet supported. There is an open issue for it #73.

However, you can achieve what you are looking for by using Object factories, this is for the toDocument mapping, for the toDto mapping you can use nested source mappings.

Your mapper would look like:

@Mapper(uses = {AccelerometerDocumentMapper.class,
            GpsDocumentMapper.class,
            GsmDocumentMapper.class},
componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.ERROR)
public interface DataDocumentMapper extends DocumentMapper<DataDTO, DataDocument> {

    @Mapping(target = "id", source = "dto")
    @Override
    DataDocument toDocument(DataDTO dto);

    @ObjectFactory
    default IdByDeviceIdAndGeneratedOn createId(DataDTO dto) {
        return dto == null ? null : new IdByDeviceIdAndGeneratedOn(dto.getDeviceId(), dto.getGeneratedOn());
    }


    @Mapping(target = "deviceId", source = "id.deviceId")
    @Mapping(target = "generatedOn", source = "id.generatedOn")
    @Override
    DataDTO toDto(DataDocument document);
}

NB: You can also make DataDocumentMapper abstract class and make the createId method protected, in case you don't want to expose it in the interface

Upvotes: 3

Igor
Igor

Reputation: 658

this is solved my problem, but this doesnt look elegant. Maybe there is more elegant way?

@Mapper(uses = {AccelerometerDocumentMapper.class,
            GpsDocumentMapper.class,
            GsmDocumentMapper.class},
imports = {IdByDeviceIdAndGeneratedOn.class},
componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.ERROR)
public interface DataDocumentMapper extends DocumentMapper<DataDTO, DataDocument> {

@Override
@Mapping(target = "id", expression = "java( new IdByDeviceIdAndGeneratedOn(dto.getDeviceId(), dto.getGeneratedOn()) )")
DataDocument toDocument(DataDTO dto);

@Override
@Mapping(target = "deviceId",    expression = "java( document.getId().getDeviceId() )")
@Mapping(target = "generatedOn", expression = "java( document.getId().getGeneratedOn() )")
DataDTO toDto(DataDocument document);
}

Upvotes: 0

Related Questions