Сопоставить dto с сущностью, полученной из базы данных, если у Dto есть Id с использованием 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

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

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

Один 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 также.

Другие вопросы по тегам