ModelMapper: выберите сопоставление на основе дочернего класса
TL; DR
я хочу использовать modelMapper таким образом, что я сопоставляю от AbstractParent к AbstractParentDTO и позже в ModelMapper-Config вызываю определенные сопоставители для каждого подкласса, а затем пропускаю остальные сопоставления (абстрактный класс).
как это возможно? Это правильный подход? Есть недоделка?
что у меня есть:
родитель сущность:
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name = "type")
public abstract class Parent {
//some more fields
}
один дочерний объект:
//Basic Lombok Annotations
@DiscriminatorValue("child_a")
public class ChildA extends Parent {
//some more fields
}
другой дочерней сущности:
@DiscriminatorValue("child_b")
public class ChildB extends Parent {
//some more fields
}
тогда у меня есть родительский класс DTO:
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME)
@JsonSubTypes({
@JsonSubTypes.Type(value = ChildA.class, name = "child_a"),
@JsonSubTypes.Type(value = ChildB.class, name = "child_b"),
public abstract class ParentDTO {
//some more fields
}
один ребенок DTO:
public class ClassADTO extends ParentDTO {
//some more fields
}
и еще DTO:
public class ClassBDTO extends ParentDTO {
//some more fields
}
в моем случае я получу DTO от контроллера и сопоставлю их с сущностями при предоставлении их службе. Мне придется сделать то же самое в 5-6 точек.
конечные точки смотреть примерно так:
@PreAuthorize(CAN_WRITE)
@PutMapping("/{id}")
public ResponseEntity<ParentDTO> update(
@PathVariable("id") UUID id,
@RequestBody @Valid ParentDTO parentDTO) {
Parent parent = parentService.update(id, parentDTO);
if (parentDTO instanceof ChildADTO) {
return ResponseEntity.ok(modelMapper.map(parent, ChildADTO.class));
} else if (parentDTO instanceof ChildBDTO) {
return ResponseEntity.ok(modelMapper.map(parent, ChildBDTO.class));
}
throw new BadRequestException("The Parent is not Valid");
}
только то, что у меня есть еще несколько детей, которые делают вещи еще более громоздкими.
что хочется:
вместо того, чтобы проверять кучу раз, какой экземпляр DTO (или сущность), я просто хочу написать, например:
modelmapper.map(parent, ParentDTO.class)
и сделайте " экземпляр..."проверьте один раз в моей конфигурации ModelMapper.
что я пробовал:
я уже имейте разные преобразователи для каждого возможного направления и картографического случая, определенного в моей конфигурации ModelMapper (поскольку они требуют более сложного отображения в любом случае).
я пытался решить мою проблему, написав один конвертер для родительских классов и установить его в качестве ModelMapper PreConverter:
//from Entity to DTO
Converter<Parent, ParentDTO> parentParentDTOConverter = mappingContext -> {
Parent source = mappingContext.getSource();
ParentDTO dest = mappingContext.getDestination();
if (source instanceof CHildA) {
return modelMapper.map(dest, ChildADTO.class);
} else if (source instanceof ChildB) {
return modelMapper.map(dest, ChildBDTO.class);
}
return null;
};
и:
modelMapper.createTypeMap(Parent.class, ParentDTO.class)
.setPreConverter(parentParentDTOConverter);
но я всегда получаю один и тот же MappingError:
1) не удалось создать экземпляр экземпляра назначение com.приложение myexample.данные.dto.ParentDTO. Обеспечить, чтобы com.приложение myexample.данные.dto.ParentDTOO не имеет частного аргумента конструктор.
что я получаю (я думаю), я не могу построить объект абстрактного класса. Но это не то, что я пытаюсь, не так ли? Я думаю, что modelMapper все остальные сопоставления после окончания моего PreConverter. Я также попытался установить его .setConverter, но всегда с тем же результат.
кто-нибудь знает, как "отключить" пользовательские сопоставления? Я не действительно хочу написать "псевдо-картографы", которые действуют как картографы и просто вызовите конкретные картографы для каждого сценария.
мой дизайн просто плохо? Как бы вы его улучшили?
это просто еще не реализовано в ModelMapper?
любая помощь и подсказки приветствуются.
3 ответов
Я бы использовал ObjectMapper вместо ModelMapper.
на родитель класс добавьте возможность получить значение дискриминатора.
//..
public class Parent {
@Column(name = "type", insertable = false, updatable = false)
private String type;
//getters and setters
}
ваш ParentDTO должен быть сопоставлен с Child (*) DTO
@JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.PROPERTY,
property = "type")
@JsonSubTypes({
@JsonSubTypes.Type(value = ChildADTO.class, name = "child_a"),
@JsonSubTypes.Type(value = ChildBDTO.class, name = "child_b")
})
public abstract class ParentDTO {
// ..
}
в сервисе/методе преобразования добавьте сопоставитель объектов с ignore unknown (игнорировать то, что вы не объявили в своем классе DTO)
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
просто звоните :
Parent parent = // get from repository
ParentDTO parentDTO = objectMapper.readValue(objectMapper.writeValueAsBytes(parent), ParentDTO.class);
таким образом, ваш ParentDTO всегда создается экземпляр с правильным типом.
Ну, решение, которое я нашел, использует конвертеры. В этом случае modelMapper
Не пытайтесь создавать новый экземпляр абстрактного класса, но использует конвертер напрямую.
вы можете поместить все преобразователи в одном месте
modelMapper.createTypeMap(ChildA.class, ParentDTO.class)
.setConverter(mappingContext -> modelMapper.map(mappingContext.getSource(), ClassADTO.class));
modelMapper.createTypeMap(ChildB.class, ParentDTO.class)
.setConverter(mappingContext -> modelMapper.map(mappingContext.getSource(), ClassBDTO.class));
....
как о
TypeMap<Parent.class, ParentDTO.class> typeMap = modelMapper.createTypeMap(Parent.class, ParentDTO.class);
typeMap
.include(ChildA .class, ClassADTO .class)
.include(ChildB.class, ClassbDTO.class);
ссылка:http://modelmapper.org/user-manual/type-map-inheritance