mapper overwriting resolved object

Viewed 77

i have a mapper which uses resolve factory to turn a dto (with its id populated) to a domain entity. It all seems to work but in the generated mapper impl, it overwrites the resolved properties from whats in the dto.

mapper:

@Mapper(componentModel = "springlazy", unmappedTargetPolicy = ReportingPolicy.ERROR, unmappedSourcePolicy = ReportingPolicy.ERROR, uses = { TicketAccountResolver.class })
public interface TicketAccountMapper {
    /**
     * Converts TicketAccount domain class to DTO
     *
     * @param TicketAccountDto domain class
     * @return TicketAccount DTO class. If null provided, would return null as well.
     */
    TicketAccountDto map(TicketAccount domain);

    /**
     * Converts TicketAccount dto class to domain
     *
     * @param TicketAccountDto domain class
     * @return TicketAccount DTO class. If null provided, would return null as well.
     */
    TicketAccount map(TicketAccountDto dto);
}

resolver:

@Component
public class TicketAccountResolver {
    @Autowired
    TicketAccountRepository repository;

    @ObjectFactory
    public TicketAccount resolve(TicketAccountDto dto, @TargetType Class<TicketAccount> type) {
        if (null != dto && dto.getId() != null) {
            return repository.findById(dto.getId()).get();
        }
        // TODO not sure on best approach here, throw exception, return null, or do the below
        return new TicketAccount(dto.getId());
    }

generated mapper impl:

 @Override
    public TicketAccount map(TicketAccountDto dto) {
        if ( dto == null ) {
            return null;
        }

        TicketAccount ticketAccount = ticketAccountResolver.resolve( dto, TicketAccount.class );

        ticketAccount.setDisplayName( dto.getDisplayName() );
        ticketAccount.setId( dto.getId() );
        ticketAccount.setName( dto.getName() );

        return ticketAccount;
    }

I need it to return the ticketAccount after resolve is called, not overwrite its fields with dto values.

1 Answers

an @ObjectFactory is used to create the object. After that mapstruct will attempt to fill it with mapping actions.

Disable automapping option

You can add the following @BeanMapping(ignoreByDefault = true) to the mapping method to disable automapping for that mapping method. Only explicitly defined mappings will now be used.

BeforeMapping option

If you use @BeforeMapping you can have it return the value that you want it to have instead. In case it is not found you can return null, in that case mapstruct will create a new ticketAccount based on the dto.

    @BeforeMapping
    public TicketAccount resolve(TicketAccountDto dto, @TargetType Class<TicketAccount> type) {
        if (null != dto && dto.getId() != null) {
            return repository.findById(dto.getId()).get();
        }
        return null;
    }

would result in something like:

 @Override
    public TicketAccount map(TicketAccountDto dto) {
        if ( dto == null ) {
            return null;
        }

        TicketAccount ticketAccount = ticketAccountResolver.resolve( dto, TicketAccount.class );

        if (ticketAccount != null) { return ticketAccount; }
// no TicketAccount found, mapstruct will now attempt to initialise it.
        ticketAccount = new TicketAccount(); // if empty constructor exists, otherwise it will initialise the constructor fields first with dto values before calling it with the correct arguments.
        ticketAccount.setDisplayName( dto.getDisplayName() );
        ticketAccount.setId( dto.getId() );
        ticketAccount.setName( dto.getName() );

        return ticketAccount;
    }
Related