Today I started using MapStruct to create my Model to DTO converters for my project and i was wondering if it handled cyclic references automatically but it turned out it doesn't.
This is the converter i made to test it:
package it.cdc.snp.services.rest.giudizio;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Mappings;
import org.mapstruct.factory.Mappers;
import org.springframework.stereotype.Component;
import it.cdc.snp.dto.entita.Avvisinotifica;
import it.cdc.snp.dto.entita.Corrispondenza;
import it.cdc.snp.model.notifica.AvvisoDiNotificaModel;
import it.cdc.snp.model.notifica.NotificaModel;
import it.cdc.snp.model.procedimento.ProcedimentoModel;
@Component
@Mapper(componentModel="spring")
public interface NotificaMapper {
NotificaMapper INSTANCE = Mappers.getMapper( NotificaMapper.class );
@Mappings({
@Mapping(source = "avvisinotificas", target = "avvisinotificas"),
})
NotificaModel<ProcedimentoModel> corrispondenzaToNotificaModel(Corrispondenza notifica);
@Mappings({
@Mapping(source = "corrispondenza", target = "notifica"),
})
AvvisoDiNotificaModel avvisinotificaToAvvisoDiNotificaModel(Avvisinotifica avvisinotifica);
}
This is the test:
Notifica sourceObject1 = new Notifica();
sourceObject1.setId(new Long(1));
Avvisinotifica sourceObject2 = new Avvisinotifica();
sourceObject2.setId(new Long(11));
List<Avvisinotifica> tests= new ArrayList<>();
tests.add(sourceObject2);
sourceObject1.setAvvisinotificas(tests);
sourceObject2.setCorrispondenza(sourceObject1);
NotificaModel destObject1 = new NotificaModel<>();
Avvisinotifica destObject2 = new Avvisinotifica();
NotificaModel converted = mapper.corrispondenzaToNotificaModel(sourceObject1);
Notifica, Avvisinotifica and their respective models are simple POJOs with setters and getters so i don't think it's needed to post the code (Notifica extends Corrispondenza, if you were wondering)
this code gets into an infinite cycle, nothing very surprising here (though i hoped it'd handle these situations).
And while i think i can find an elegant way to manually handle it (i was thinking about using methods with @MappingTarget
to insert the Referenced objects ) what i was wondering is if there's some way to tell MapStruct how to automatically handle cyclic references.
Notifica and Avvisinotifica are not helping me understand your models. Thus lets say you have the above Child and Father models,
public class Child {
private int id;
private Father father;
// Empty constructor and getter/setter methods ommitted.
}
public class Father {
private int x;
private List<Child> children;
// Empty constructor and getter/setter methods ommitted.
}
public class ChildDto {
private int id;
private Father father;
// Empty constructor and getter/setter methods ommitted.
}
public class FatherDto {
private int id;
private List<Child> children;
// Empty constructor and getter/setter methods ommitted.
}
You should create a Mapper like this,
@Mapper
public abstract class ChildMapper {
@AfterMapping
protected void ignoreFathersChildren(Child child, @MappingTarget ChildDto childDto) {
childDto.getFather().setChildren(null);
}
public abstract ChildDto myMethod(Child child);
}
The @AfterMapping annotation means that the method will be imported inside the generated source, after the mapping of the properties. Thus, the Mapper implementation will be like this,
@Component
public class ChildMapperImpl extends ChildMapper {
@Override
public ChildDto myMethod(Child child) {
if ( child == null ) {
return null;
}
ChildDto childDto = new ChildDto();
childDto.setId( child.getId() );
childDto.setFather( child.getFather() );
ignoreFathersChildren( child, childDto );
return childDto;
}
}
In this implementation the child has the parent set. This means that a cycle reference exists, but using the ignoreFathersChildren(child, childDto)
method we remove the reference (we set it as null).
===
Using the mapstruct version 1.2.0.Final you can do it better,
@Mapper
public interface ChildMapper {
@Mappings({
// @Mapping(target = "father", expression = "java(null)"),
@Mapping(target = "father", qualifiedByName = "fatherToFatherDto")})
ChildDto childToChildDto(Child child);
@Named("fatherToFatherDto")
@Mappings({
@Mapping(target = "children", expression = "java(null)")})
FatherDto fatherToFatherDto(Father father);
}