问题
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.
回答1:
There is no detection or special handling of cases like this in MapStruct yet, but there is a feature request for it: #469. If you got any ideas how to deal with cycles, please drop a comment at that issue.
回答2:
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).
===
Update
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);
}
回答3:
At least in mapstruct 1.3 you can use the following:
The solution is widely inspired by https://github.com/mapstruct/mapstruct-examples/tree/master/mapstruct-mapping-with-cycles/src/main/java/org/mapstruct/example/mapper
Define a Context class (widely inspired by https://github.com/mapstruct/mapstruct-examples/blob/master/mapstruct-mapping-with-cycles/src/main/java/org/mapstruct/example/mapper/CycleAvoidingMappingContext.java ):
/**
* An implementation to track cycles in graphs to be used as {@link Context} parameter.
*
*/
public class CycleAvoidingMappingContext {
private Map<Object, Object> knownInstances = new IdentityHashMap<Object, Object>();
/**
* Gets an instance out of this context if it is already mapped.
*
* @param source
* given source
* @param targetType
* given target type.
* @return Returns the resulting type.
*/
@BeforeMapping
public <T> T getMappedInstance(Object source, @TargetType Class<T> targetType) {
return targetType.cast(knownInstances.get(source));
}
/**
* Puts an instance into the cache, so that it can be remembered to avoid endless mapping.
*
* @param source
* given source
* @param target
* given target
*/
@BeforeMapping
public void storeMappedInstance(Object source, @MappingTarget Object target) {
knownInstances.put( source, target );
}
}
In each mapper, which maps classes with cyclic references, add this org.mapstruct.Context
:
/**
* Mapper. Automatically implemented by mapstruct.
*
*/
@Mapper
public interface SomeObjWithCyclesMapper {
/**
* instance.
*/
SomeObjWithCyclesMapper INSTANCE = Mappers.getMapper(SomeObjWithCyclesMapper.class);
/**
* Mapper method to map entity to domain. Automatically implemented by mapstruct.
*
* @param entity
* given entity.
* @param context
* context to avoid cycles.
* @return Returns the domain object.
*/
SomeObjWithCycles entityToDomain(SomeObjWithCyclesEntity entity, @Context CycleAvoidingMappingContext context);
/**
* Mapper method to map domain object to entity. Automatically implemented by mapstruct.
*
* @param domain
* given domain object.
* @param context
* context to avoid cycles.
* @return Returns the entity.
*/
SomeObjWithCyclesEntity domainToEntity(SomeObjWithCycles domain, @Context CycleAvoidingMappingContext context);
}
来源:https://stackoverflow.com/questions/36223752/prevent-cyclic-references-when-converting-with-mapstruct