javamappingdata-transfer-objectsmapstruct

Map a dto to an entity retrieved from database if Dto has Id using MapStruct


I'm using MapStruct to make dto <-> entity mapping. The same mappers are used to create and update entities from dtos. A verification of the dto's id is done to know whether a new entity must be created (id == null) or it should be retrieved from database (id != null) .

I'm actually using MapperDecorator as a workaround. Example :

Mapper

@Mapper
@DecoratedWith(UserAccountDecorator.class)
public interface UserAccountMapper {

    UserAccountDto map(User user);

    User map(UserAccountDto dto);

    User map(UserAccountDto dto, @MappingTarget User user);
}

Decorator

public abstract class UserAccountDecorator implements UserAccountMapper {

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

    @Autowired
    private UserRepository userRepository;

    @Override
    public User map(UserAccountDto dto) {
        if (dto == null) {
            return null;
        }

        User user = new User();
        if (dto.getId() != null) {
            user = userRepository.findOne(dto.getId());
        }

        return delegate.map(dto, user);
    }

}

But this solution becomes heavy due to the fact that a decorator must be created for each mapper.

Is there any good solution to perform that ?


I'm using :

  1. MapStruct : 1.1.0

Solution

  • I solved my problem by following the advice of Gunnar in the comment.

    I moved to MapStruct 1.2.0.Beta1 and created a UserMapperResolver like below

    @Component
    public class UserMapperResolver {
    
        @Autowired
        private UserRepository userRepository;
    
        @ObjectFactory
        public User resolve(BaseUserDto dto, @TargetType Class<User> type) {
            return dto != null && dto.getId() != null ? userRepository.findOne(dto.getId()) : new User();
        }
    
    }
    

    Which I use then in my UserMapper :

    @Mapper(uses = { UserMapperResolver.class })
    public interface BaseUserMapper {
    
        BaseUserDto map(User user);
    
        User map(BaseUserDto baseUser);
    
    }
    

    The generated code is now :

    @Override
        public User map(BaseUserDto baseUser) {
            if ( baseUser == null ) {
                return null;
            }
    
            User user = userMapperResolver.resolve( baseUser, User.class );
    
            user.setId( baseUser.getId() );
            user.setSocialMediaProvider( baseUser.getSocialMediaProvider() );
    ...
    }
    

    Works well !