Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mapstruct 'aftermapping' not called

Tags:

mapstruct

The problem is that, methods annotated with @AfterMapping are not called at all. From the testToEntityMapping it goes to toEntity method but it doesn't call any of the toEntityAfterMapping() methods. Why ? Is it possible ? how can I achieve that using MapStruct ?

(Here I have prepared a scenario that is meaningless but it fully captures the essence of my question)
Entity:

public class Ford {
    private String color;
    private String market;
    private int totalWidth;

    //getters and setters omitted for brevity
}

Dtos:

public abstract class FordDto {
    public String market;
    public String color;

    //getters and setters omitted for brevity
}

public class EuropeanFordDto extends FordDto{
    private int totalWidth;

    public int getTotalWidth() {
        return totalWidth + 2;//"+2" for EU market
    }
    //setter omitted for brevity
}

public class AmericanFordDto extends FordDto{
    private int totalWidth;

    public int getTotalWidth() {
        return totalWidth + 1;//"+1" for US market
    }

    //setter omitted for brevity
}

Mappers:

public abstract class FordMapper<D extends FordDto> {
    public Ford toEntity(D dto) {

        /* fill in fields common to both ford versions */

        final Ford ford = new Ford();

        ford.setColor(dto.getColor());
        ford.setMarket(dto.getMarket());

        return ford;
    }
}
@Mapper(componentModel = "spring")
public abstract class EuropeanFordMapper extends FordMapper<EuropeanFordDto> {

    @AfterMapping
    public void toEntityAfterMapping(final EuropeanFordDto dto, @MappingTarget final Ford entity) {

        /* Fill in fields related to european version of the ford */

        entity.setTotalWidth(dto.getTotalWidth());
    }
}
@Mapper(componentModel = "spring")
public abstract class AmericanFordMapper extends FordMapper<AmericanFordDto> {

    @AfterMapping
    public void toEntityAfterMapping(final AmericanFordDto dto, @MappingTarget final Ford entity) {

        /* Fill in fields related to american version of the ford */

        entity.setTotalWidth(dto.getTotalWidth());
    }
}

Service:

@Service
public class CarService {

    @Autowired
    private AmericanFordMapper americanFordMapper;
    @Autowired
    private EuropeanFordMapper europeanFordMapper;

    public void testToEntityMapping(final FordDto dto) {

        if (dto instanceof AmericanFordDto) {
            americanFordMapper.toEntity((AmericanFordDto) dto);
        } else {
            europeanFordMapper.toEntity((EuropeanFordDto) dto);
        }
    }
}
like image 714
user3529850 Avatar asked Dec 08 '18 23:12

user3529850


People also ask

What is a MapStruct?

MapStruct is an open-source Java-based code generator which creates code for mapping implementations. It uses annotation-processing to generate mapper class implementations during compilation and greatly reduces the amount of boilerplate code which would regularly be written by hand.

How do you ignore property in MapStruct?

To do this, we use the MapStruct unmappedTargetPolicy to provide our desired behavior when there is no source field for the mapping: ERROR: any unmapped target property will fail the build – this can help us avoid accidentally unmapped fields. WARN: (default) warning messages during the build. IGNORE: no output or ...

Why is MapStruct used?

MapStruct is a code generator tool that greatly simplifies the implementation of mappings between Java bean types based on a convention over configuration approach. The generated mapping code uses plain method invocations and thus is fast, type-safe, and easy to understand.

What is <UNK>Context MapStruct?

By marking it @Context you essentially tell MapStruct to ignore the parameter for mapping, but pass it along in underlying method calls. Also, although MapStruct can handle multiple source arguments, it cannot call multiple source parameter methods to handle nested mappings.


2 Answers

My solution to the same problem was that I forgot to add 'default' keyword to my @AfterMapping method (I used interface). After that method appeared in generated code.

And don't forget to run mvn/gradle clean and compile after making changes.

like image 165
Eduard Streltsov Avatar answered Oct 10 '22 10:10

Eduard Streltsov


Ok, it was simpler than I thought it is.

public interface FordMapper<D extends FordDto> {
    
    @Mapping(target = "totalWidth", ignore=true)
    public abstract Ford toEntity(D dto);
}

you can even peek in the implementation that is in toEntity() method, there is a call to toEntityAfterMapping(), thus everything is correct and in accordance with our desired result.

like image 1
user3529850 Avatar answered Oct 10 '22 11:10

user3529850