Search code examples
javaspringmapstructmapper

SpringBoot MapStruct Mapping Entity to DTO with Relationship Entities


I am using the MapStruct library for mapping objects, and I need to translate from an entity to a dto, but there is a problem, for example, that the entity stores animal types as a list of objects, and the dto stores an array with the ids of these types. how can this be done correctly? I did it, but only manually using loops. MapStruct is something new for me, so a lot of things are not clear, I hope for your help, thanks!

I have this class:

@FieldDefaults(level = AccessLevel.PRIVATE)
@Table(name = "animal")
public class Animal {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    Long id;

    @NotEmpty
    @MinCollectionSize
    @ElementOfCollectionNotNull
    @ManyToMany(fetch = FetchType.EAGER)
    @JoinTable(name = "animal_types",
    joinColumns = {@JoinColumn(name = "animal_id",referencedColumnName = "id")},
    inverseJoinColumns = {@JoinColumn(name = "type_id",referencedColumnName = "id")})
    List<AnimalType> animalTypes;

    @NotNull
    @Min(1)
    Float weight;

    @NotNull
    @Min(1)
    Float length;

    @NotNull
    @Min(1)
    Float height;

    @NotNull
    @NotGender
    AnimalGender gender;

    AnimalLifeStatus lifeStatus;

    TimestampWithTimeZoneJdbcType chippingDateTime;

    @NotNull
    @ManyToOne
    @JoinColumn(name = "chipper_id")
    Account chipperId;

    @NotNull
    @ManyToOne
    @JoinColumn(name = "chipping_location_id")
    Location chippingLocationId;

    @OneToMany(mappedBy = "animal")
    List<AnimalLocation> visitedLocations;

    TimestampWithTimeZoneJdbcType deathDateTime;

}

And i need this Entity mapping to this DTO:

@FieldDefaults(level = AccessLevel.PRIVATE)
public class AnimalResponse {
    Long id;
    Long[] animalTypes;
    Float weight;
    Float length;
    Float height;
    AnimalGender gender;
    AnimalLifeStatus lifeStatus;
    TimestampWithTimeZoneJdbcType chippingDateTime;
    Integer chippedId;
    Long chippingLocationId;
    Long[] visitedLocations;
    TimestampWithTimeZoneJdbcType deathDateTime;
}

This is my Mapper Interfaces:

public interface BaseMapper<ENTITY, DTO> {

    DTO toDto(ENTITY entity);
    ENTITY toEntity(DTO dto);
    List<DTO> toDtoList(List<ENTITY> entityList);
    List<ENTITY> toEntityList(List<DTO> dtoList);

}
@Mapper
public interface AnimalMapper extends BaseMapper<Animal, AnimalResponse> {
    AnimalMapper INSTANCE = Mappers.getMapper(AnimalMapper.class);
}

Solution

  • @Mapper
    public interface AnimalMapper extends BaseMapper<Animal, AnimalResponse> {
        
        AnimalMapper INSTANCE = Mappers.getMapper(AnimalMapper.class);
        
        @Override
        @Named("toDto")
        @Mapping(source = "animalTypes", target = "animalTypes", qualifiedByName = "toArray")
        AnimalResponse toDto(Animal entity);
        
        @Override
        @IterableMapping(qualifiedByName = "toDto")
        List<AnimalResponse> toDtoList(List<Animal> entityList);
        
        @Override
        @Named("toEntity")
        @Mapping(source = "animalTypes", target = "animalTypes", qualifiedByName = "toArrayList")
        Animal toEntity(AnimalResponse dto);
        
        @IterableMapping(qualifiedByName = "toEntity")
        List<Animal> toEntityList(List<AnimalResponse> dtoList);
        
        @Named("toArray") 
        default Long[] toArray(List<AnimalType> animalTypes) { 
            return animalTypes.stream()
                              .map(AnimalType::getId)
                              .toArray(size -> new Long[size]);
        }
        
        @Named("toArrayList") 
        default List<AnimalType> toArrayList(Long[] animalTypes) { 
            return Arrays.stream(animalTypes)
                         .map(AnimalType::new) // new object just for example purposes, you can instead call the repository findById
                         .collect(Collectors.toList());
        }
        
    }
    

    With the following animal:

    Animal(id=1, animalTypes=[AnimalType(id=1), AnimalType(id=2), AnimalType(id=3)])
    

    the generated response by calling toDto

    AnimalResponse response = AnimalMapper.INSTANCE.toDto(animal);
    

    will be:

    AnimalResponse(id=1, animalTypes=[1, 2, 3])