java - 使用 MapStruct 转换时防止循环引用

标签 java cyclic-reference mapstruct

今天我开始使用 MapStruct 为我的项目创建我的模型到 DTO 转换器,我想知道它是否自动处理循环引用但事实证明它没有。

这是我用来测试它的转换器:

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);


}

这是测试:

        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 和它们各自的模型是带有 setter 和 getter 的简单 POJO,所以我认为不需要发布代码(Notifica 扩展了 Corrispondenza,如果您想知道的话)

这段代码进入了一个无限循环,这里没有什么特别令人惊讶的(尽管我希望它能处理这些情况)。 虽然我认为我可以找到一种优雅的方法来手动处理它(我正在考虑使用带有 @MappingTarget 的方法来插入引用的对象),但我想知道是否有某种方法可以告诉 MapStruct 如何自动处理循环引用。

最佳答案

Notifica 和 Avvisinotifica 无法帮助我理解您的模型。因此,假设您有上述子模型和父模型,

public class Child {
    private int id;
    private Father father;
    // Empty constructor and getter/setter methods omitted.
}

public class Father {
    private int x;
    private List<Child> children;
    // Empty constructor and getter/setter methods omitted.
}

public class ChildDto {
    private int id;
    private FatherDto father;
    // Empty constructor and getter/setter methods omitted.
}

public class FatherDto {
    private int id;
    private List<ChildDto> children;
    // Empty constructor and getter/setter methods omitted.
}  

你应该像这样创建一个映射器,

@Mapper
public abstract class ChildMapper {

    @AfterMapping
    protected void ignoreFathersChildren(Child child, @MappingTarget ChildDto childDto) {
        childDto.getFather().setChildren(null);
    }

    public abstract ChildDto myMethod(Child child);
}

Mapstuct的初始版本

最好按照下面的方法。此解决方案假定 ChildDto::father 属性是 Father 类型,而不是 FatherDto,这不是正确的数据架构。
@AfterMapping注释意味着在属性映射之后,该方法将被导入到生成的源代码中。因此,Mapper 实现将是这样的,

@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;
    }
}

在此实现中,子项具有父项集。这意味着存在循环引用,但使用 ignoreFathersChildren(child, childDto) 方法我们删除了引用(我们将其设置为 null)。

更新1

使用 mapstruct 版本 1.2.0.Final 你可以做得更好,

@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);
}

更新2

使用 mapstruct 版本 1.4.2.Final 你可以做得更好,

@Named("FatherMapper")
@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE)
public interface FatherMapper {

    @Named("toDto")
    @Mappings
    FatherDto toDto(Father father);

    @Named("toDtoWithoutChildren")
    @Mappings({
         @Mapping(target = "children", expression = "java(null)")})
    FatherDto toDtoWithoutChildren(Father father);
}

@Named("ChildMapper")
@Mapper(componentModel = "spring", unmappedTargetPolicy = ReportingPolicy.IGNORE, uses = {FatherMapper.class})
public interface ChildMapper {

    @Named("toDto")
    @Mappings({
         @Mapping(target = "father", qualifiedByName = {"FatherMapper", "toDtoWithoutChildren"})})
    ChildDto toDto(Child child);

    @Named("toDtoWithoutFather")
    @Mappings({
         @Mapping(target = "father", expression = "java(null)")})
    ChildDto toDtoWithoutFather(Child child);
}

关于java - 使用 MapStruct 转换时防止循环引用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36223752/

相关文章:

C++ 循环依赖与邻接表表示的混淆

c++ - 编译C++多对多类关系

java - 映射器没有使用另一个映射器,如何使用另一个映射器的映射器?

java - 发现映射属性的映射方法不明确

java - Play framework 2.5.0 Websockets 示例

java - 如何操作一串 INSERT 数据并以正确的方式分隔列的值?

循环依赖 - 结构和函数指针相互引用

java - 如何使用 MapStruct 将 String 转换为 Map?

java - 在 CLASSPATH 上永久添加文件

java - 为自定义类实现 hashcode 和 equals