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

后端 未结 2 619
执笔经年
执笔经年 2020-12-31 20:08

I\'m using MapStruct to make dto <-> entity mapping. The same mappers are used to create and update en

相关标签:
2条回答
  • 2020-12-31 20:35

    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 !

    0 讨论(0)
  • 2020-12-31 20:57

    MapStruct alone can't do that. However, with some generics and a main abstract class you can make your life easier.

    You need one generic interface. It must not be annotated with @Mapper, because if it is MapStruct will try to generate an implementation and it will fail. It cannot generate generic mappers.

    public interface GenericMapper<E, DTO> {
    
        DTO map(E entity);
    
        E map(DTO dto);
    
        E map(DTO dto, @MappingTarget E entity);
    }
    

    Then you need one abstract class where you'll have your logic.

    public abstract class AbstractGenericMapper<E, DTO> implements GenericMapper<E, DTO> {
    
        @Autowired
        private Repository<E> repository;
    
        @Override
        public final E map (DTO dto) {
            if (dto == null) {
                return null;
            }
    
            // You can also use a Java 8 Supplier and pass it down the constructor
            E entity = newInstance();
            if (dto.getId() != null) {
                user = repository.findOne(dto.getId());
            }
    
            return map(dto, entity);
        }
    
        protected abstract E newInstance();
    }
    

    And then each of your mappers will only need to extend this abstract class.

    @Mapper
    public abstract class UserAccountMapper extends AbstractGenericMapper<User, UserDto> {
    
        protected User newInstance() {
            return new User();
        }
    }
    

    MapStruct will then generate an implementation for your mapper and you will only have to extend the AbstractGenericMapper for the future. Of course you will need to adapt the generic parameters so you can at least get the id from the via some interface maybe. If you have different type of ids then you will have to add that generic parameter to the AbstractGenericMapper as well.

    0 讨论(0)
提交回复
热议问题