Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use decorated method in Mapstruct collection mapper?

I am using MapStruct to map from a JPA entity to a POJO DTO, in a Spring app with dependency injection.

I have added some additional processing of the DTO to a method in a decorator as specified in the doc.

It works fine for mapping a single entity. But I also have a mapping for a collection (set) of these entities and the method is called automatically when a collection of those entities is found in a relationship.

However the generated collection mapping method does not use the decorated method to map each entity, is just uses the "vanilla" generated method on the delegate. Here is the code of the generated method :

@Override
public Set<DimensionItemTreeDTO> missionSetToTreeDtoSet(Set<Mission> set)  {
    return delegate.missionSetToTreeDtoSet( set );
}

The delegate method itself is not aware of the decorator and calls the individual item mapping method on itself :

@Override
public Set<DimensionItemTreeDTO> missionSetToTreeDtoSet(Set<Mission> set) {
    if ( set == null ) {
        return null;
    }

    Set<DimensionItemTreeDTO> set__ = new HashSet<DimensionItemTreeDTO>();
    for ( Mission mission : set ) {
        set__.add( missionToTreeDto( mission ) ); //here the decorator is not called !
    }

    return set__;
}

...and the decorated method is never called for the items inside the collection.

Is there a way I can make Mapstruct use the decorator method in the collection mappings, short of writing the collection method manually in my decorator (which works but is verbose and defeats the purpose of having MapStruct in the first place which is not to have to write this kind of code) ?

like image 802
Pierre Henry Avatar asked May 16 '16 11:05

Pierre Henry


People also ask

How do I map a MapStruct collection?

In general, mapping collections with MapStruct works the same way as for simple types. Basically, we have to create a simple interface or abstract class, and declare the mapping methods. Based on our declarations, MapStruct will generate the mapping code automatically.

What is MappingTarget in MapStruct?

Annotation Type MappingTargetDeclares a parameter of a mapping method to be the target of the mapping. Not more than one parameter can be declared as MappingTarget . NOTE: The parameter passed as a mapping target must not be null .

What is componentModel in MapStruct?

Enclosing class: MappingConstants public static final class MappingConstants.ComponentModel extends Object. Specifies the component model constants to which the generated mapper should adhere. It can be used with the annotation Mapper.componentModel() or MapperConfig.componentModel()

How do I ignore unmapped properties in MapStruct?

We can ignore unmapped properties in several mappers by setting the unmappedTargetPolicy via @MapperConfig to share a setting across several mappers. We should note that this is a simple example showing the minimal usage of @MapperConfig, which might not seem much better than setting the policy on each mapper.


2 Answers

I found the solution to my problem : actually my use case was better suited for the MapStruct @AfterMapping methods, I used it and it is now working fine for all cases :

@Mapper
public abstract class ConstraintsPostProcessor {

    @Inject
    private UserService userService; // can use normal Spring DI here

    @AfterMapping
    public void setConstraintsOnMissionTreeDTO(Mission mission, @MappingTarget MissionDTO dto){ // do not forget the @MappingTarget annotation or it will not work
        dto.setUser(userService.getCurrentUser()); // can do any additional logic here, using services etc.
    }
}

And in the main mapper :

@Mapper(uses = {ConstraintsPostProcessor.class}) // just add the previous class here in the uses attribute
public interface DimensionMapper {
    ...
}
like image 90
Pierre Henry Avatar answered Sep 28 '22 16:09

Pierre Henry


You should create another decorator for sub object. Suppose you have Book class which has one Author as sub object.

public class Book {

private int id;

private Author author;

getters and setters
}

public class BookDto {

private int bookId;

private AuthorDto author;

getters and setters
}

public class Author {

private int id;

private String name;

getters and setters

}

public class AuthorDto {

private int authorId;

private String name;

getters and setters
}

And we create BookMapper and BookMapperDecorator

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

@Mapping(target = "bookId", ignore = true)
BookDto toDto(Book book);
}

public abstract class BookMapperDecorator implements BookMapper{

@Autowired
@Qualifier("delegate")
private BookMapper delegate;

@Override
public BookDto toDto(Book book) {
    BookDto bookDto = delegate.toDto(book);
    bookDto.setBookId(book.getId());
    return bookDto;
}
}

And also AuthorMapper and AuthorMapperDecorator

@Mapper(componentModel = "spring")
@DecoratedWith(AuthorMapperDecorator.class)
public interface AuthorMapper {

@Mapping(target = "authorId", source = "id")
@Mapping(target = "name", ignore = true)
AuthorDto toDto(Author author);
}

public abstract class AuthorMapperDecorator implements AuthorMapper{

@Autowired
@Qualifier("delegate")
private AuthorMapper delegate;

@Override
public AuthorDto toDto(Author author) {
    AuthorDto authorDto = delegate.toDto(author);
    authorDto.setName(author.getName().toUpperCase());
    return authorDto;
}
}

The important parts here are uses = AuthorMapper.class (which says that one mapper uses another one and you don't have to include Author parsing method in BookMapper)

componentModel = "spring" (Which is responsible for bean injection and understands the @Qualifier("delegate") )

You can also ignore one field in mapper and set it in decorator as we did with bookId

like image 43
Vahan Yeghyan Avatar answered Sep 28 '22 15:09

Vahan Yeghyan