Отображение объектов с двунаправленными связями с Mapstruct

#java #stack-overflow #bidirectional #mapstruct

#java #переполнение стека #двунаправленный #mapstruct

Вопрос:

Интересно, может ли и как Mapstruct помочь с отображением объектов с двунаправленными отношениями (в моем случае один ко многим):

 public class A{
     private Set<B> listB;
}

public class B{
     private A a;
}
  

Отображение из / в объект дает StackOverflowError . (я бы ожидал, что это произойдет).
С другой стороны, закрытые проблемы с Mapstruct 469 и 1163, по-видимому, подразумевают, что mapstruct не будет поддерживать его напрямую.
Я попробовал этот пример:

https://github.com/mapstruct/mapstruct-examples/tree/master/mapstruct-mapping-with-cycles

Но это просто не работает. С применением или без применения «CycleAvoidingMappingContext» ошибка stackoverflowerror остается неизменной.

Итак, как отображать объекты с помощью циклов и использовать mapstruct? (я хочу избежать полного отображения вручную)

Комментарии:

1. Можете ли вы предоставить код, который у вас не работает? Пример, который вы связали, должен работать.

2. Я попробовал это с кодом из примера. Используя этот класс: github.com/mapstruct/mapstruct-examples/blob/master /…

3. Возможно, показ картографа, который вы пробовали, поможет нам лучше понять, где ошибка. Я только что попробовал (и ответил) с предоставленной вами информацией, и она работает так, как ожидалось.

Ответ №1:

Для того, чтобы отображение работало, вы можете попробовать использовать следующий mapper:

 @Mapper
public interface MyMapper {

    A map(A a, @Context CycleAvoidingMappingContext context);

    Set<B> map(Set<B> b, @Context CycleAvoidingMappingContext context);

    B map(B b, @Context CycleAvoidingMappingContext context);
}
  

Если методов для отображения из Set<B> в Set<B> не существует, то набор в A не будет отображен.

Ответ №2:

Тем временем я нашел решение: сначала я игнорирую рассматриваемое поле и отображаю его в «AfterMapping». В моем случае я хочу также отобразить дочерний элемент независимо. В моем случае @Context единственное содержит Boolean ( boolean не работает), которое сообщает, где я вошел в цикл ( startedFromPru pru является родительским):

Mapper для родительского (содержит набор лицензий):

 @Mapper(componentModel = "spring", uses = {DateTimeMapper.class, LicenseMapper.class, CompanyCodeMapper.class, ProductHierarchyMapper.class})
public abstract class ProductResponsibleUnitMapper {

    @Autowired
    private LicenseMapper licenseMapper;

    @Mappings({@Mapping(target = "licenses", ignore = true)})
    public abstract ProductResponsibleUnit fromEntity(ProductResponsibleUnitEntity entity, @Context Boolean startedFromPru);

    @Mappings({@Mapping(target = "licenses", ignore = true)})
    public abstract ProductResponsibleUnitEntity toEntity(ProductResponsibleUnit domain, @Context Boolean startedFromPru);

    @AfterMapping
    protected void mapLicenseEntities(ProductResponsibleUnit pru, @MappingTarget ProductResponsibleUnitEntity pruEntity, @Context Boolean startedFromPru){
        if(startedFromPru) {
            pruEntity.getLicenses().clear(); //probably not necessary
            pru.getLicenses().stream().map(license -> licenseMapper.toEntity(license, startedFromPru)).forEach(pruEntity::addLicense);
        }
    }

    @AfterMapping
    protected void mapLicenseEntities(ProductResponsibleUnitEntity pruEntity, @MappingTarget ProductResponsibleUnit pru, @Context Boolean startedFromPru){
        if(startedFromPru) {
            pru.getLicenses().clear(); //probably not necessary
            pruEntity.getLicenses().stream().map(licenseEntity -> licenseMapper.fromEntity(licenseEntity, startedFromPru)).forEach(pru::addLicense);
        }
    }

}
  

Картограф для дочерних объектов:

 @Mapper(componentModel = "spring", uses = { DateTimeMapper.class, CompanyCodeMapper.class, ProductHierarchyMapper.class,
        CountryCodeMapper.class })
public abstract class LicenseMapper {

    @Autowired
    private ProductResponsibleUnitMapper pruMapper;

    @Mappings({ @Mapping(source = "licenseeId", target = "licensee"), @Mapping(target = "licensor", ignore = true) })
    public abstract License fromEntity(LicenseEntity entity, @Context Boolean startedFromPru);


    @Mappings({ @Mapping(source = "licensee", target = "licenseeId"), @Mapping(target = "licensor", ignore = true) })
    public abstract LicenseEntity toEntity(License domain, @Context Boolean startedFromPru);

    @AfterMapping
    protected void mapPru(LicenseEntity licenseEntity, @MappingTarget License license,
            @Context Boolean startedFromPru) {
        //only call ProductResponsibleUnitMapper if mapping did not start from PRU -> startedFromPru is false
        if (!startedFromPru) {
            license.setLicensor(pruMapper.fromEntity(licenseEntity.getLicensor(), startedFromPru));
        }
    }

    @AfterMapping
    protected void mapPru(License license, @MappingTarget LicenseEntity licenseEntity,
            @Context Boolean startedFromPru) {
        //only call ProductResponsibleUnitMapper if mapping did not start from PRU -> startedFromPru is false
        if (!startedFromPru) {
            licenseEntity.setLicensor(pruMapper.toEntity(license.getLicensor(), startedFromPru));
        }
    }