Сопоставьте dto с сущностью, полученной из базы данных, если Dto имеет идентификатор с помощью MapStruct

Я использую MapStruct для dto <-> entity сопоставления. Те же средства сопоставления используются для создания и обновления объектов из dtos. Проверка идентификатора dto выполняется, чтобы узнать, должна ли быть создана новая сущность (id == null) или она должна быть получена из базы данных (id! = Null).

На самом деле я использую MapperDecorator в качестве временного решения. Пример :

Картограф

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

    UserAccountDto map(User user);

    User map(UserAccountDto dto);

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

Декоратор

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);
    }

}

Но это решение становится тяжелым из-за того, что для каждого маппера нужно создавать декоратор.

Есть ли хорошее решение для этого?


Я использую :

  1. MapStruct: 1.1.0

person Radouane ROUFID    schedule 21.02.2017    source источник


Ответы (2)


Я решил свою проблему, следуя советам Gunnar в комментарий.

Я перешел на MapStruct 1.2.0.Beta1 и создал UserMapperResolver, как показано ниже

@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();
    }

}

Что я использую затем в моем UserMapper:

@Mapper(uses = { UserMapperResolver.class })
public interface BaseUserMapper {

    BaseUserDto map(User user);

    User map(BaseUserDto baseUser);

}

Сгенерированный код теперь:

@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() );
...
}

Работает хорошо !

person Radouane ROUFID    schedule 22.02.2017
comment
Привет, Радуан, РУФИД, спасибо, что поделились своим окончательным решением. У меня небольшой вопрос: допустим, я хочу повлиять на свой преобразователь объекта, полученного из моего репозитория в поле объекта объекта. Можно ли это сделать в объектных фабриках MapStruct? Не нашел похожих примеров использования. EG: У меня есть пользователь, у которого есть объект Phone в отношениях OneToOne. Если я получаю UserDto, который содержит phoneId, и я хочу сопоставить его с пользователем с реальным объектом Phone. Благодарность ! - person Alex; 07.02.2018
comment
Вы можете ввести PhoneRepository в свой UserResolver и получить объект телефона из базы данных. @Autowired private UserRepository userRepository; @Autowired private PhoneREpository phoneRe; @ObjectFactory public User resolve(BaseUserDto dto, @TargetType Class<User> type) { User user = userRepository.findOne(dto.getId()); user.setPhone(phoneRep.getFIndOne(dto.getPhoneId())) return user; } - person Radouane ROUFID; 08.02.2018
comment
Да, я бы сделал это, но в случае создания пользователя? Я возвращаю новый элемент пользователя только с информацией о телефоне, но ничего больше? Вызывается ли mapper после завершения объекта с фактическим DTO, или мы должны выполнять всю логику сопоставления в преобразователе? - person Alex; 08.02.2018
comment
ObjectFactory создает / выбирает объект, в котором будет выполнено сопоставление. Вы можете проверить сгенерированный код в моем решении. - person Radouane ROUFID; 08.02.2018

Сам по себе MapStruct не может этого сделать. Однако с помощью некоторых универсальных шаблонов и основного абстрактного класса вы можете облегчить себе жизнь.

Вам нужен один общий интерфейс. Он не должен быть аннотирован @Mapper, потому что если это MapStruct попытается сгенерировать реализацию, и это не удастся. Он не может генерировать универсальные картографы.

public interface GenericMapper<E, DTO> {

    DTO map(E entity);

    E map(DTO dto);

    E map(DTO dto, @MappingTarget E entity);
}

Тогда вам понадобится один abstract класс, в котором у вас будет своя логика.

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();
}

И тогда каждому из ваших картографов нужно будет только расширить этот abstract класс.

@Mapper
public abstract class UserAccountMapper extends AbstractGenericMapper<User, UserDto> {

    protected User newInstance() {
        return new User();
    }
}

Затем MapStruct сгенерирует реализацию для вашего картографа, и вам нужно будет только расширить AbstractGenericMapper на будущее. Конечно, вам нужно будет адаптировать общие параметры, чтобы вы могли, по крайней мере, получить идентификатор через какой-либо интерфейс. Если у вас другой тип идентификаторов, вам также необходимо добавить этот общий параметр в AbstractGenericMapper.

person Filip    schedule 21.02.2017
comment
Спасибо, Филип, попробую это решение! - person Radouane ROUFID; 22.02.2017
comment
Альтернативой могут быть методы фабрики объектов, получающие исходные объекты, включенные в MapStruct 1.2.0.Beta1 (выпущен вчера). У вас может быть общий фабричный метод, который либо создает экземпляр нового объекта, либо загружает его из базы данных, в зависимости от состояния DTO (присутствует идентификатор или нет). - person Gunnar; 22.02.2017
comment
Очень красиво, это решение лучше. Спасибо, Гуннар, ты молодец! - person Radouane ROUFID; 22.02.2017
comment
Я также думал о новой возможности фабричных методов возвращать объект. Однако, если это используется, MapStruct немедленно вернет объект, если не null - person Filip; 22.02.2017