当前位置: 首页 > 知识库问答 >
问题:

java 映射结构 1.3.1 忽略双向 DTO 映射列表中的属性

蒋默
2023-03-14

我正在与MapStruct的循环依赖问题作斗争。由于循环依赖,我一直有一个StackOverFlow错误。为了避免它,我只需要排除一个列表的属性。我发现了这个:https://github.com/mapstruct/mapstruct/issues/933我在网上查了很久,我很惊讶我找不到任何完整的例子来展示使用MapStruct的双向DTO映射(除了使用< code > @ Context cycleavadingmappingcontext ,对我不起作用)。

[EDIT]:由于MapStruct聊天,我找到了一个解决方法,我将其添加EditorMapper中

这是我的案例,我想这很常见:我有两个DTO相互引用:

public class BookDTO {

    private Long id;

    private String title;

        //... other properties

    //@JsonManagedReference --> not necessary anymore
    private EditorDTO editor;
}
public class EditorDTO {

    private Long id;
    private String name;

        //...other properties

    //@JsonBackReference --> not necessary anymore
    private List< BookDTO > bookList;
}

而且我需要MapStruct能够将属性编辑器从编辑器的书单中排除,然后避免无限循环。以下是我目前拥有的地图:

@Mapper
public interface BookMapper {

    BookMapper INSTANCE = Mappers.getMapper( BookMapper.class );

    @Mapping( target = "editor.bookList", ignore = true)
    BookDTO toDTO( BookEntity bookEntity );

    @Named( "NoEditor" )
    @Mapping(target = "editor", ignore = true)
    BookDTO toDTONoEditor( BookEntity bookEntity );

    List<BookDTO> toDTOList( List<BookEntity> bookEntityList );

    @Named( "NoEditor" )
    @IterableMapping(qualifiedByName="NoEditor")
    List<BookDTO> toDTOListNoEditor( List<BookEntity> bookEntityList );

    @Mapping( target = "editor.bookList", ignore = true)
    BookEntity toEntity( BookDTO bookDTO );

    List<BookEntity> toEntityList( List<BookDTO> bookDTOList );
}
@Mapper(uses = BookMapper.class)
public interface EditorMapper {

    EditorMapper INSTANCE = Mappers.getMapper( EditorMapper.class );

    @Named( "NoEditor" )
    @Mapping(target = "bookList", qualifiedByName = "NoEditor")
    EditorDTO toDTO( EditorEntity editorEntity );

    @Named( "NoEditor" )
    @IterableMapping(qualifiedByName="NoEditor")
    List<EditorDTO> toDTOList( List< EditorEntity > editorEntityList );

    EditorEntity toEntity( EditorDTO editorDTO );

    List<EditorEntity> toEntityList( List< EditorDTO > editorDTOList );
}

[编辑]:现在可以用了但是不是100%干净(更多细节请看我贴的回答)

我也在mappers里试过这种方法,但是对我的pb没有任何影响。

BookDTO toDTO( BookEntity bookEntity, @Context CycleAvoidingMappingContext context );

有人知道我做错了什么吗?非常感谢!:)


共有2个答案

宇文修文
2023-03-14

你打赌它有帮助!谢谢你的例子。我会在这里添加我的完整和更多细节,因为我认为关于这个的信息太少了:

在双向关系的情况下,这种映射会由于循环引用而触发StackOverflowError。

示例:类Recipe、Book和Ingredient,它们是双向的一对多和多对多。

  • 一个食谱有很多配料,但只在一本书中提到。
  • 一本书里有很多食谱。
  • 一种配料只在一个配方中使用(假设一种配料也具有固定其数量、计量单位等的属性,因此它确实只针对一个配方)。
    public class Recipe {
        Long id;
        // ... Other recipe properties go here
        Book book;
        Set<Ingredient> ingredients;
    }
    
    public class Book {
        Long id;
        // ... Other book properties go here
        Set<Recipe> recipes;
    }
    
    public class Ingredient {
        Long id;
        // ... Other ingredient properties go here
        Recipe recipe;
    }

我假设你也会有具有相同属性的DTO类,但当然会引用它们相应的DTO类。

这些将是用于从实体类映射到DTO类的默认Mapper设置(在这种情况下不依赖于Spring):

// MapStruct can handle primitive and standard classes like String and Integer just fine, but if you are using custom complex objects it needs some instructions on how it should map these
    @Mapper(uses = {BookMapper.class, IngredientMapper.class})
    public interface RecipeMapper {
        RecipeMapper INSTANCE = Mappers.getMapper( RecipeMapper.class );

        RecipeDTO toDTO(Recipe recipe);

        Recipe toEntity(RecipeDTO recipeDTO);
    }

    @Mapper(uses = {RecipeMapper.class, IngredientMapper.class})
    public interface BookMapper {
        BookMapper INSTANCE = Mappers.getMapper( BookMapper.class );

        BookDTO toDTO(Book book);

        Book toEntity(BookDTO book);
    }

    @Mapper(uses = {RecipeMapper.class, BookMapper.class})
    public interface IngredientMapper {
        IngredientMapper INSTANCE = Mappers.getMapper( IngredientMapper.class );

        IngredientDTO toDTO(Ingredient ingredient);

        Ingredient toEntity(IngredientDTO ingredientDTO);
    }

如果您就此打住,并试图以这种方式映射这些类,您将会遇到StackOverflowError,这是由于您现在已经定义了循环引用(recipe contains ingredients具有属性Recipe,而Recipe具有ingredients...).这样的默认映射器设置只能在没有双向关系的情况下使用,否则也会触发反向映射。

你可以把它写下来,就像A -

    < li >向下钻取到前端的关联对象:例如。显示食谱的配料列表 < li >保存对象时保持相反的关系:例如。如果你能画出-

像定义一个-

@IterableMapping(限定名 = ”

@Mapping(target = "PropertyName ",qualifiedByName = "

@映射(目标 = "[.]", 忽略=true)可用于指示对象的属性根本不应该被映射。因此,这可用于完全省略复杂对象的(集合),或者直接忽略单个(而不是集合)相关的复杂对象内部的属性,以防不需要它们。

如果不使用限定的ByName 属性和匹配的 @Named() 批注,则如果在 Mapper 接口中有多个具有相同返回类型和输入参数类型的方法,则映射将不会编译时出现有关不明确映射的错误。

如果您使用命名映射,使用与@Naming注释值匹配的方法名称可能是一个很好的做法。

因此,我们将首先记下想要的行为,然后对其进行编码:

1. When mapping a Recipe, we will need to map the book property in such a way that its inverse relation to recipes is mapped without the book property the second time
    Recipe A -> Book X  -> Recipe A (without book property value as this would close the cycle)
        -> Recipe B (without book property value, as same mapping is used for all these recipes unfortunately as we don't know up front which one will cause the cyclic reference)...
            -> Ingredients I (without recipe property value as they would all point back to A)
                             
2. When mapping a Book, we will need to map the recipes property in such a way that its inverse relation to book isn't mapped as it will point back to the same book.
        Book X -> Recipe A (without book property as this would close the cycle)
                    -> Ingredients (without recipe property as all these will point back to Recipe A)
                        -> Recipe B (without book property, as same mapping is used for all these and all could potentially close the cycle)
                        -> Recipe C
                
3. When mapping an Ingredient, we will need to map the recipe property in such a way that its inverse relation to ingredient isn't mapped as one of those ingredients will point back to the same ingredient

recipe中的book属性需要在没有recipes属性的情况下进行映射,因为其中一个属性也会返回到recipe。

    @Mapper(uses = {BookMapper.class, IngredientMapper.class})
    public interface RecipeMapper {
        RecipeMapper INSTANCE = Mappers.getMapper( RecipeMapper.class );

        @Named("RecipeSetIgnoreBookAndIngredientChildRecipes")
        @IterableMapping(qualifiedByName = "RecipeIgnoreBookAndIngredientChildRecipes")
        Set<RecipeDTO> toDTOSetIgnoreBookAndIngredientChildRecipes(Set<Recipe> recipes);

        @Named("RecipeSetIgnoreIngredientsAndBookChildRecipe")
        @IterableMapping(qualifiedByName = "RecipeIgnoreIngredientsAndBookChildRecipe")
        Set<RecipeDTO> toDTOSetIgnoreIngredientsAndBookChildRecipe(Set<Recipe> recipes);
                                
        // In this mapping we will ignore the book property and the recipe property of the Ingredients to break the mapping cyclic references when we are mapping a book object
        // Don't forget to add the matching inverse mapping from DTO to Entity, this is basically just a copy with switch input parameter and return types
        @Named("RecipeIgnoreBookAndIngredientChildRecipes")
        @Mappings({
            @Mapping(target = "book", ignore = true),                                               // book is a single custom complex object (not a collection), so we can directly ignore its child properties from there
            @Mapping(target = "ingredients", qualifiedByName = "IngredientSetIgnoreRecipes"),       // ingredients is a collection of complex objects, so we can't directly ignore its child properties as in the end, a Mapper needs to be defined to Map a single POJO into another
        })
        RecipeDTO toDTOIgnoreBookAndIngredientChildRecipes(Recipe recipe);

        @Named("RecipeIgnoreIngredientsAndBookChildRecipe")
        @Mappings({
            @Mapping(target = "book.recipes", ignore = true),
            @Mapping(target = "ingredients", ignore = true),
        })
        RecipeDTO toDTOIgnoreIngredientsAndBookChildRecipe(Recipe recipe);

        // Don't forget to add the matching inverse mapping from DTO to Entity, this is basically just a copy with switch input parameter and return types
        @Mappings({
            @Mapping(target = "book.recipes", ignore = true),                                       // book is a single custom complex object (not a collection), so we can directly ignore its child properties from there
            @Mapping(target = "ingredients", qualifiedByName = "IngredientSetIgnoreRecipes"),       // ingredients is a collection of complex objects, so we can't directly ignore its child properties as in the end, a Mapper needs to be defined to Map a single POJO into another
        })
        RecipeDTO toDTO(Recipe recipe);
        
        @Named("RecipeSetIgnoreBookAndIngredientChildRecipes")
        @IterableMapping(qualifiedByName = "RecipeIgnoreBookAndIngredientChildRecipes")
        Set<Recipe> toEntitySetIgnoreBookAndIngredientChildRecipes(Set<RecipeDTO> recipeDTOs);
        
        @Named("RecipeSetIgnoreIngredientsAndBookChildRecipe")
        @IterableMapping(qualifiedByName = "RecipeIgnoreIngredientsAndBookChildRecipe")
        Set<Recipe> toEntitySetIgnoreIngredientsAndBookChildRecipe(Set<RecipeDTO> recipeDTOs);
        
        @Mappings({
            @Mapping(target = "book.recipes", ignore = true),                                       // book is a single custom complex object (not a collection), so we can directly ignore its child properties from there
            @Mapping(target = "ingredients", qualifiedByName = "IngredientSetIgnoreRecipes"),       // ingredients is a collection of complex objects, so we can't directly ignore its child properties as in the end, a Mapper needs to be defined to Map a single POJO into another
        })
        Recipe toEntity(RecipeDTO recipeDTO);
        
        @Named("RecipeIgnoreBookAndIngredientChildRecipes")
        @Mappings({
            @Mapping(target = "book", ignore = true),                                               // book is a single custom complex object (not a collection), so we can directly ignore its child properties from there
            @Mapping(target = "ingredients", qualifiedByName = "IngredientSetIgnoreRecipes"),       // ingredients is a collection of complex objects, so we can't directly ignore its child properties as in the end, a Mapper needs to be defined to Map a single POJO into another
        })
        Recipe toEntityIgnoreBookAndIngredientChildRecipes(RecipeDTO recipeDTO);
        
                                @Named("RecipeIgnoreIngredientsAndBookChildRecipe")
        @Mappings({
            @Mapping(target = "book.recipes", ignore = true),
            @Mapping(target = "ingredients", ignore = true),
        })
        Recipe toEntityIgnoreIngredientsAndBookChildRecipe(RecipeDTO recipeDTO);
        
    }



    @Mapper(uses = {RecipeMapper.class, IngredientMapper.class})
    public interface BookMapper {
        BookMapper INSTANCE = Mappers.getMapper( BookMapper.class );
        
        @Mappings({
            @Mapping(target = "recipes", qualifiedByName = "RecipeSetIgnoreBookAndIngredientChildRecipes"),
        })
        BookDTO toDTO(Book book);

        @Mappings({
            @Mapping(target = "recipes", qualifiedByName = "RecipeSetIgnoreBookAndIngredientChildRecipes"),
        })
        Book toEntity(BookDTO book);
    }



    @Mapper(uses = {RecipeMapper.class, BookMapper.class})
    public interface IngredientMapper {
        IngredientMapper INSTANCE = Mappers.getMapper( IngredientMapper.class );

        // Don't forget to add the matching inverse mapping from DTO to Entity, this is basically just a copy with switch input parameter and return types
        @Named("IngredientSetIgnoreRecipes")
        IterableMapping(qualifiedByName = "IngredientIgnoreRecipes")                                // Refer to the mapping for a single object in the collection
        Set<IngredientDTO> toDTOSetIgnoreRecipes(Set<Ingredient> ingredients);

        // Don't forget to add the matching inverse mapping from DTO to Entity, this is basically just a copy with switch input parameter and return types
        @Named("IngredientIgnoreRecipes")
        @Mappings({
            @Mapping(target = "recipes", ignore = true),                                            // ignore the recipes property entirely
        })
        IngredientDTO toDTOIgnoreRecipes(Ingredient ingredient);

        @Mappings({
            @Mapping(target = "recipes", qualifiedByName = "RecipeSetIgnoreIngredientsAndBookChildRecipe")
        })
        IngredientDTO toDTO(Ingredient ingredient);

        @Named("IngredientSetIgnoreRecipes")
        IterableMapping(qualifiedByName = "IngredientIgnoreRecipes")                                // Refer to the mapping for a single object in the collection
        Set<Ingredient> toEntitySetIgnoreRecipes(Set<IngredientDTO> ingredientDTOs);

        @Named("IngredientIgnoreRecipes")
        @Mappings({
            @Mapping(target = "recipes", ignore = true),
        })
        Ingredient toEntityIgnoreRecipes(IngredientDTO ingredientDTO);

        @Mappings({
            @Mapping(target = "recipes", qualifiedByName = "RecipeSetIgnoreIngredientsAndBookChildRecipe")
        })
        Ingredient toEntityIgnoreRecipes(IngredientDTO ingredientDTO);
    }

用法

<ENTITY_NAME>DTO <eNTITY_NAME>DTO = <ENTITY_NAME>Mapper.INSTANCE.toDTO( <eNTITY_NAME> );`
严言
2023-03-14
匿名用户

[编辑]:我也添加了一个双向多对多映射的解决方案,多亏了https://gitter.im/mapstruct/mapstruct-users#,,我才能够得到这个解决方案。[编辑]:我还有没有意识到的错误。现已在本次更新中得到纠正。我不得不:-将< code>uses attribut添加到< code > editor Mapper :< code > @ Mapper(component model = " spring ",uses = BookMapper.class) -在< code>BookMapper中添加替代方法,如< code>toDTONoEditor或< code > totolistnoeditor ,其中我忽略了< code>editor属性。-在< code>EditorMapper中映射这些替代方法-对每个循环依赖项都一样

解决方案如下:

预订

public class BookDTO {

    private Long id;

    private String title;

        //... other properties

    private EditorDTO editor;
    private List< CategoryDTO > categoryList;
}

编辑DTO

public class EditorDTO {

    private Long id;
    private String name;

        //...other properties

    private List< BookDTO > bookList;
}

类别

public class CategoryDTO {

    private Long id;

    private String category;

    private List< BookDTO > bookList;
}

书签制作者

@Mapper(componentModel = "spring", uses = {CategoryMapper.class, EditorMapper.class})
public interface BookMapper {


    @Named( "NoBook" )
    @Mappings( {
            @Mapping(target = "categoryList", qualifiedByName = "NoBook"),
            @Mapping( target = "editor.bookList", ignore = true)
    } )
    BookDTO toDTO( BookEntity bookEntity );

    @Named( "NoEditor" )
    @Mappings( {
            @Mapping(target = "editor", ignore = true),
            @Mapping(target = "categoryList", qualifiedByName = "NoBook")
    } )
    BookDTO toDTONoEditor( BookEntity bookEntity );

    @Named( "NoCategory" )
    @Mappings( {
            @Mapping(target = "categoryList", ignore = true),
            @Mapping(target = "editor", qualifiedByName = "NoBook")
    } )
    BookDTO toDTONoCategory( BookEntity bookEntity );


    @Named( "NoBook" )
    @IterableMapping(qualifiedByName="NoBook")
    List<BookDTO> toDTOList( List<BookEntity> bookEntityList );

    @Named( "NoEditor" )
    @IterableMapping(qualifiedByName="NoEditor")
    List<BookDTO> toDTOListNoEditor( List<BookEntity> bookEntityList );

    @Named( "NoCategory" )
    @IterableMapping(qualifiedByName="NoCategory")
    List<BookDTO> toDTOListNoCategory( List<BookEntity> bookEntityList );


    @Named( "NoBook" )
    @Mappings( {
            @Mapping(target = "categoryList", qualifiedByName = "NoBook"),
            @Mapping( target = "editor.bookList", ignore = true)
    } )
    BookEntity toEntity( BookDTO bookDTO );

    @Named( "NoCategory" )
    @Mapping(target = "categoryList", ignore = true)
    BookEntity toEntityNoCategory( BookDTO bookDTO );


    @Named( "NoBook" )
    @IterableMapping(qualifiedByName="NoBook")
    List<BookEntity> toEntityList( List<BookDTO> bookDTOList );

    @Named( "NoCategory" )
    @IterableMapping(qualifiedByName="NoCategory")
    List<BookEntity> toEntityListNoCategory( List<BookDTO> bookDTOList );
}

编辑器映射器

@Mapper(componentModel = "spring", uses = BookMapper.class)
public interface EditorMapper {

    @Named( "NoEditor" )
    @Mapping(target = "bookList", qualifiedByName = "NoEditor")
    EditorDTO toDTO( EditorEntity editorEntity );

    @Named( "NoBook" )
    @Mapping(target = "bookList", ignore = true)
    EditorDTO toDTONoBook( EditorEntity editorEntity );


    @Named( "NoEditor" )
    @IterableMapping(qualifiedByName="NoEditor")
    List< EditorDTO > toDTOList( List< EditorEntity > editorEntityList );

    @Named( "NoBook" )
    @IterableMapping(qualifiedByName="NoBook")
    List< EditorDTO > toDTOListNoBook( List< EditorEntity > editorEntityList );

    @Named( "NoBook" )
    @Mapping(target = "bookList", qualifiedByName = "NoBook")
    EditorEntity toEntity( EditorDTO editorDTO );

    @Named( "NoBook" )
    @IterableMapping(qualifiedByName="NoBook")
    List< EditorEntity > toEntityList( List< EditorDTO > editorDTOList );
}

类别映射器

@Mapper(componentModel = "spring",uses = BookMapper.class)
public interface CategoryMapper {


    @Named( "NoCategory" )
    @Mapping(target = "bookList", qualifiedByName = "NoCategory")
    CategoryDTO toDTO( CategoryEntity categoryEntity );

    @Named( "NoBook" )
    @Mapping(target = "bookList", ignore = true)
    CategoryDTO toDTONoBook( CategoryEntity categoryEntity );


    @Named( "NoCategory" )
    @IterableMapping(qualifiedByName="NoCategory")
    List<CategoryDTO> toDTOList( List< CategoryEntity > categoryEntityList );

    @Named( "NoBook" )
    @IterableMapping(qualifiedByName="NoBook")
    List<CategoryDTO> toDTOListNoBook( List< CategoryEntity > categoryEntityList );


    @Named( "NoCategory" )
    @Mapping(target = "bookList", qualifiedByName = "NoCategory")
    CategoryEntity toEntity( CategoryDTO categoryDTO );

    @Named( "NoBook" )
    @Mapping(target = "bookList", ignore = true)
    CategoryEntity toEntityNoBook( CategoryDTO categoryDTO );


    @Named( "NoCategory" )
    @IterableMapping(qualifiedByName="NoCategory")
    List<CategoryEntity> toEntityList( List< CategoryDTO > categoryDTOList );

    @Named( "NoBook" )
    @IterableMapping(qualifiedByName="NoBook")
    List<CategoryEntity> toEntityListNoBook( List< CategoryDTO > categoryDTOList );

}

这样,循环依赖关系在进入无限循环之前就被打破了。但是,它有99%的满意度,因为编辑器书籍对象并不完全干净。编辑器包含书列表,嗯。但 bookList 中的每本书仍包含一个空编辑器字段。对于 Book 对象,反之亦然。但它似乎是一个去序列化问题,而不是地图结构问题。以下是 Json 结果

编者ˌ编辑

{
  "id": 1,
  "name": "Folio",
  "coordinates": null,
  "bookList": [
    {
      "id": 1,
      "title": "Le cycle de Fondation, I : Fondation",
      "categoryList": [
        {
          "id": 5,
          "category": "LITERATURE&FICTION"
        }
      ],
      "language": "French",
      "isbn": 2070360539,
      "publicationDate": null,
      "numberOfPages": 416,
      "authorList": [],
      "libraryList": [
        {
          "id": 2,
          "name": "Library2",
          "coordinates": null
        },
        {
          "id": 1,
          "name": "Library1",
          "coordinates": null
        }
      ],
      "editor": null
    }
  ]
}

{
  "id": 1,
  "title": "Le cycle de Fondation, I : Fondation",
  "categoryList": [
    {
      "id": 5,
      "category": "LITERATURE&FICTION",
      "bookList": null
    }
  ],
  "language": "French",
  "isbn": 2070360539,
  "publicationDate": null,
  "numberOfPages": 416,
  "authorList": [],
  "libraryList": [
    {
      "id": 2,
      "name": "Library2",
      "coordinates": null
    },
    {
      "id": 1,
      "name": "Library1",
      "coordinates": null
    }
  ],
  "editor": {
    "id": 1,
    "name": "Folio",
    "coordinates": null,
    "bookList": null
  }
}

种类

{
  "id": 1,
  "category": "CHILDREN",
  "bookList": [
    {
      "id": 5,
      "title": "Le petit prince",
      "categoryList": null,
      "language": "French",
      "isbn": 9782070612758,
      "publicationDate": null,
      "numberOfPages": 120,
      "authorList": [],
      "libraryList": [
        {
          "id": 2,
          "name": "Library2",
          "coordinates": null
        },
        {
          "id": 1,
          "name": "Library1",
          "coordinates": null
        }
      ],
      "editor": null
    }
  ]
}

希望对此有所帮助:)

 类似资料:
  • 我有一个实体,看起来像这样: 输入数据是一个

  • 问题内容: 映射双向列表时,我不了解Hibernate的行为。Hibernate生成的SQL语句对我来说并不是最佳的。有人可以启发我吗? 情况如下:我有一对多的父子关系。我将此关系与双向列表映射。 根据《Hibernate注释参考指南》(第7章:与索引集合的双向关联),映射应如下所示: 但是在这种情况下,Hibernate在保留一个孩子的父母时会产生三个SQL语句: 第三条语句似乎是多余的,因为并

  • 问题内容: 我想要一个从键到对象,反之亦然的数据结构(与仅在单个方向上映射的HashMaps不同)。一个想法可能是将HashMap存储在其内部以进行反向查找,但这将是一种低效的方法。 双向映射的最佳实现是什么? 问题答案: 最简单的想法:包装器类包含2个映射,第二个包含交换的键/值。您将保持O(1)的复杂性,并且将仅使用稍微更多的内存,因为您(可能)将对象保留在那里。

  • 下面的例子中,我有一个单独的域层和一个单独的持久层。我使用Mapstruct进行映射,当从域映射到实体或从实体映射到域时,会出现堆栈溢出,因为双向引用总是被调用- 用于映射的类非常基本

  • 我有两张桌子。后 和喜欢 在这些对象之间进行hibernate注释映射,以便在类似于Post bean的。。。。 就像豆子一样 问题 该关联是否由Post{@OneToOne}和like{@ManyToOne}正确? 获取类型是Lazy,但仍然得到依赖循环。为什么? 尝试 要删除依赖关系循环,我尝试了 {@xmltransive} {@JsonIgnore} {@JsonManagedRefere

  • 我的实体如下所示: 我的问题是: 为什么会这样,即使在我添加食谱和房子之间的联系之前没有发生? 我怎样才能修好它? 原因是什么?