Пытаюсь замапить BookDTO.java в Book.java используя MapStruct, но сталкиваюсь с ошибкой.
Вот тут все мои DTO:
BookDTO.java:
@Data
@Document
public class BookDTO {
@Id
private UUID id;
private BookSpecificationsDTO specifications;
}
BookSpecificationsDTO interface:
public interface BookSpecificationsDTO {
BookSpecificationsTypeDTO type = null;
default BookSpecificationsTypeDTO getType() {
return type;
}
}
BookSpecificationsTypeDTO:
public enum BookSpecificationsTypeDTO{
CLASSIC,
PHANTASY
}
ClassicBookSpecificationDTO:
@Data
public class ClassicBookSpecificationDTO implements BookSpecificationsDTO {
private BookSpecificationsTypeDTO type = BookSpecificationsTypeDTO.CLASSIC;
private String genre;
private int length;
}
PhantasyBookSpecificationDTO:
@Data
public class PhantasyBookSpecificationDTO implements BookSpecificationsDTO {
private BookSpecificationsTypeDTO type = BookSpecificationsTypeDTO.PHANTASY;
private String genre;
}
Тут все мои модели, которые в моем случае являются идентичными DTO:
Book.java:
@Data
@Document
public class Book {
@Id
private UUID id;
private BookSpecifications specifications;
}
BookSpecifications interface:
public interface BookSpecifications {
BookSpecificationsType type = null;
default BookSpecificationsType getType() {
return type;
}
}
BookSpecificationsType:
public enum BookSpecificationsType{
CLASSIC,
PHANTASY
}
ClassicBookSpecification:
@Data
public class ClassicBookSpecification implements BookSpecifications {
private BookSpecificationsType type = BookSpecificationsType.CLASSIC;
private String genre;
private int length;
}
PhantasyBookSpecification:
@Data
public class PhantasyBookSpecification implements BookSpecifications {
private BookSpecificationsType type = BookSpecificationsType.PHANTASY;
private String genre;
}
Я хотел замапить свой BookDTO.java в Book.java model. Вот тут mapper class:
@Mapper(componentModel = "spring", nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
public interface BookMapper {
@Mapping(target = "id", expression = "java(java.util.UUID.randomUUID())")
Book toBook(BookDTO bookDTO);
}
Вот тут ошибки, кторые я получаю:
java: The return type BookSpecificationsDTO is an abstract class or interface. Provide a non abstract / non interface result type or a factory method.
и
java: Can't map property "BookSpecificationsDTO specifications" to "BookSpecifications specifications". Consider to declare/implement a mapping method: "BookSpecifications map(BookSpecificationsDTO value)".
Насколько я понял, проблема заключается в том, что в классе Book и BookDTO находится поле BookSpecifications, которое является интерфейсом, имеет тип ClassicBookSpecification или PhantasyBookSpecification в зависимости от введенного параметра type, и MapStruct не может замапить поле, которое является итерфейсом. Возможно ли как-то данный маппинг реализовать?
1 ответ 1
Вообще, так как тут необходима информация времени выполнения, не существует простого и одновременно "чистого" способа решить эту задачу.
"Грязный" способ заключается в использовании рефлексии. Вы можете сделать следующее:
- для каждой пары классов-наследников определить свой собственный метод трансформации;
- определить общий метод трансформации интерфейсов, в котором в зависимости от типа переданного параметра дергать тот или иной метод из группы (1)
Выглядеть это может примерно так:
@Mapper(componentModel = "spring", nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
public interface BookMapper {
@Mapping(target = "id", expression = "java(java.util.UUID.randomUUID())")
Book toBook(BookDTO bookDTO);
/**
* ...
*/
ClassicBookSpecification toBookSpecification(ClassicBookSpecificationDTO specDTO);
/**
* ...
*/
PhantasyBookSpecification toBookSpecification(PhantasyBookSpecification specDTO);
/**
* ...
*/
default BookSpecifications toBookSpecificationGeneric(BookSpecificationsDTO specDTO) {
if(specDTO == null) {
return null;
}
switch(specDTO.getType()) {
case CLASSIC:
return toBookSpecification((ClassicBookSpecification)specDTO);
case PHANTASY:
return toBookSpecification((PhantasyBookSpecification)specDTO);
}
throw new InvalidArgumentException(specDTO.getType());
}
}
Второй способ - способ без рефлексии, - это написание маппера-визитера.
public interface BookSpecifications {
BookSpecificationsType type = null;
default BookSpecificationsType getType() {
return type;
}
BookSpecification toBookSpecification(BookMapper mapper);
}
// ...
@Data
public class ClassicBookSpecificationDTO implements BookSpecificationsDTO {
// ...
public BookSpecifications toBookSpecification(BookMapper mapper) {
return mapper.toBookSpecification(this);
}
}
// ...
@Data
public class PhantasyBookSpecificationDTO implements BookSpecificationsDTO {
// ...
public BookSpecifications toBookSpecification(BookMapper mapper) {
return mapper.toBookSpecification(this);
}
}
// ...
@Mapper(componentModel = "spring", nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
public interface BookMapper {
@Mapping(target = "id", expression = "java(java.util.UUID.randomUUID())")
Book toBook(BookDTO bookDTO);
/**
* ...
*/
ClassicBookSpecification toBookSpecification(ClassicBookSpecificationDTO specDTO);
/**
* ...
*/
PhantasyBookSpecification toBookSpecification(PhantasyBookSpecification specDTO);
/**
* ...
*/
default BookSpecifications toBookSpecificationGeneric(BookSpecificationsDTO specDTO) {
return specDTO.toBookSpecification(this);
}
}
Плюсы и минусы у каждого подхода - свои, а применимость должна выбираться исходя из конкретной задачи