Сопоставить 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);
}
}
Но это решение становится тяжелым из-за того, что декоратор должен быть создан для каждого преобразователя.
Есть ли хорошее решение для этого?
Я использую:
- 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
также.