今天,我开始使用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及其各自的模型都是带有setters和getter的简单POJO,因此我认为不需要发布代码(如果您想知道,Notifica扩展了Corrispondenza )
这段代码进入了一个无限循环,这里没什么好奇怪的(虽然我希望它能处理这些情况)。虽然我认为我可以找到一种优雅的方法来手动处理它(我正在考虑使用@MappingTarget方法来插入引用对象),但我想知道的是,是否有什么方法可以告诉MapStruct如何自动处理循环引用。
发布于 2016-03-29 07:24:41
在MapStruct中还没有发现或特殊处理这种情况,但是有一个特性请求:#469。如果你对如何处理周期有任何想法,请不要对这个问题发表评论。
发布于 2017-02-14 17:50:41
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::FatherDto属性为父类型,而不是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,最后您可以做得更好,
@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.最后,您可以做得更好,
@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);
}发布于 2020-10-01 18:41:20
实际上,这种使用CycleAvoidingMappingContext的方法并不适用于我使用MapStruct 1.3.1版本。因为我找不到很多有用的例子,所以我把我的解决方案放在这里,让其他人去找。
在双向关系的情况下,由于循环引用,这种映射可以触发StackOverflowError。
示例:菜谱、书籍和配料的类,它们是双向的一对多和多对多的.
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的影响,因为您现在已经定义了循环引用(菜谱包含有包含成分的属性配方的成分.)。这种默认的Mapper设置只能在没有双向关系的情况下使用,而这种关系也会触发逆映射。
你可以把它写下来就像A -> B -> A -> B -> A.关于对象映射,我的经验表明,您应该能够将此映射为:a -> B -> A(不包括这一次打破循环的关系),将实体映射为DTO,DTO映射到实体映射。这使你能够:
定义像A -> B -> A这样的映射(不包括这一次打破循环的关系)将归结为定义单独的映射,以便在想要打破循环的时候从映射中排除相关的复杂对象。
@IterableMapping(qualifiedByName = "")用于映射复杂对象的集合,该集合引用单个复杂对象的映射。
@ mapping (PropertyName= "PropertyName",qualifiedByName = "")可以用来指向另一种映射,在映射复杂对象集合时(当您想打破循环时),这种映射排除了逆关系。
@(目标= ".",忽略=真)可以用来表示对象的属性根本不应该被映射。因此,这可以用于完全忽略复杂对象的集合,或者直接忽略单个(而不是集合)相关复杂对象中的属性,以防不需要这些属性。
如果您不使用qualifiedByName属性和匹配的@()注释,如果在Mapper接口中有多个具有相同返回类型和输入参数类型的方法,则映射将不会编译时出现关于模糊映射的错误。
在使用命名映射的情况下,使用与@Named注释值匹配的方法名可能是一种很好的做法。
因此,我们将首先记录想要的行为,然后编写代码::
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菜谱中的book属性将需要映射,因为其中一个属性还将循环回食谱,因此不需要使用菜谱属性。
@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> );`https://stackoverflow.com/questions/36223752
复制相似问题