Those mapstruct usages you don't know -- mapstruct custom mapping

Those mapstruct usages you don't know -- mapstruct custom mapping

preface

mapStruct is a very useful field mapping tool, which can help you automatically generate code to complete field mapping. However, sometimes you need to apply custom logic before or after some mapping methods.
For example, the sex field found in the database is a number 0 and 1. We need to convert it into male and female, put it into Dto and pass it to the front end. My previous practice is that mapstruct performs field conversion after mapping. However, this will add a lot of set get code to the service layer. If the conversion is between sets, we need to traverse it again.
MapStruct provides two methods: decorator allows type safe customization of specific mapping methods, and pre mapping and post mapping life cycle methods allow general customization of mapping methods of a given source or target type, which can help us solve this problem

Using decorator mapping

mapstruct uses decorator mode to perform additional operations on the mapped results.

To use decorators, you need to apply decorators to mapper classes, and use @ DecorateWith to specify decorator classes

@Mapper
@DecoratedWith(PersonMapperDecorator.class)
public interface PersonMapper {

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

    PersonDto personToPersonDto(Person person);

    AddressDto addressToAddressDto(Address address);
}

The decorator class must implement the mapper interface and can be declared as an abstract class that implements only those methods that want to be mapped.

Use example

public abstract class PersonMapperDecorator implements PersonMapper {

    private final PersonMapper delegate;

    public PersonMapperDecorator(PersonMapper delegate) {
        this.delegate = delegate;
    }

    @Override
    public PersonDto personToPersonDto(Person person) {
        PersonDto dto = delegate.personToPersonDto( person );
        dto.setFullName( person.getFirstName() + " " + person.getLastName() );
        return dto;
    }
}

The mapper uses Spring injection

If your mapping class uses the Spring container, you need to inject @ Qualifier("delegate") into the decorator class
(the name must be delegate, because this is the default name of the generated implementation class bean)

public abstract class PersonMapperDecorator implements PersonMapper {

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

     @Override
     public PersonDto personToPersonDto(Person person) {
         PersonDto dto = delegate.personToPersonDto( person );
         dto.setName( person.getFirstName() + " " + person.getLastName() );

         return dto;
     }
 }

The decorator class does not need us to call. When we use it, we can call the mapping class as before. (it is recommended to inject beans using constructors)

@Autowired
private PersonMapper personMapper;

public PersonDto  getPersonInfo(Long personId) {
	Person person = personDao.queryPerson(personId);
	return personMapper.personToPersonDto(person);
}

Mapping customization using pre mapping and post mapping methods

When customizing the mapper, the decorator may not always meet the requirements.
For example, if you need to perform custom mapping not only on several specific methods, but also on all methods that map a specific supertype,
In this case, you can use the callback method called before the mapping starts or after the mapping completes.

Use example

@Mapper
public abstract class VehicleMapper {

    @BeforeMapping
    protected void flushEntity(AbstractVehicle vehicle) {
        // You can do some operations before mapping the mapper class
    }

    @AfterMapping
    protected void fillTank(AbstractVehicle vehicle, @MappingTarget AbstractVehicleDto result) {
        result.fuelUp( new Fuel( vehicle.getTankCapacity(), vehicle.getFuelType() ) );
    }

    public abstract CarDto toCarDto(Car car);
}

//mapper automatically generated code
public class VehicleMapperImpl extends VehicleMapper {

    public CarDto toCarDto(Car car) {
        flushEntity( car );

        if ( car == null ) {
            return null;
        }

        CarDto carDto = new CarDto();

        fillTank( car, carDto );

        return carDto;
    }
}

@Methods annotated by BeforeMapping and @ AfterMapping. If there are parameters, they can only be used in the method

  1. The return type (non void) of the method can be assigned to the return type of the mapping method
  2. Method calls are generated when all parameters can be assigned by mapped source or target parameters:

(a little windy, explain with the example above)

The parameter of the flush entity method in the @ BeforeMapping annotation above is AbstractVehicle type. The parameter Car in toCarDto is a subclass of this type. If it can be passed in, the method will be generated.

The above @ AfterMapping annotation method has two parameters. You can see that one is annotated with @ MappingTarget, which means mapping target, and the mapping target class of toCarDto is CarDto, which is a subclass of AbstractVehicleDto. When the allocable conditions are met, the method is generated.

The above methods do not meet the first condition because their return value is null,
If the method return value of @ BeforeMapping and @ AfterMapping annotation is not null, there must also be parameters assignable to generate.

Call order

The order of method calls is mainly determined by their variants:

@ BeforeMapping@MappingTarget A method without parameters is called before any source check is made and any new bean is constructed.

@ BeforeMapping@MappingTarget After the new target bean is constructed, a method with parameters is called.

@AfterMapping calls the method at the end of the mapping method before the last return statement.

Tags: Java Project

Posted on Sun, 21 Nov 2021 16:08:45 -0500 by purtip3154