Mapstruct — как преобразовать параметр строки DTO в объект Entity?

Я новичок в Mapstruct, и я пытаюсь понять это правильно.

Чего я хочу добиться, так это преобразовать параметр строки DTO (carModel) в его сущность, получить ее с помощью службы и репозитория.

Проблема в том, что класс Mapper, сгенерированный Mapstruct, пытается внедрить класс Service с аннотацией @Autowired, но это не работает. Услуга нулевая.

Вот мой @Mapper класс:

@Mapper(componentModel = "spring", uses = CarModelService.class)
public interface KitMapper extends EntityMapper<KitDTO, Kit> {
    KitMapper INSTANCE = Mappers.getMapper(KitMapper.class);

    @Mapping(source = "weight", target = "systemWeight")
    @Mapping(source = "carModel", target = "carModel")
    Kit toEntity(KitDTO kitDTO);
}

public interface EntityMapper<D, E> {
    E toEntity(D dto);
    List<E> toEntity(List<D> dtoList);
}

Класс @Service:

@Service
@Transactional
public class CarModelService {
    private final CarModelRepository carModelRepository;

    @Transactional(readOnly = true)
    public CarModel findByName(String name) {
        return carModelRepository.findByName(name).orElse(null);
    }
}

Класс @Repository:

@Repository
public interface CarModelRepository extends JpaRepository<CarModel, Long> {
    Optional<CarModel> findByName(String carModelName);
}

Классы DTO и Entity:

public class KitDTO {
    private String id;
    private String carModel; // e.g. "Ferrari Monza"
    ....
}
@Entity
@Table(name = "kit")
public class Kit implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
    @SequenceGenerator(name = "sequenceGenerator")
    @Column(name = "id")
    private Long id;

    @ManyToOne
    private CarModel carModel;

    ...
}
@Entity
@Table(name = "car_model")
public class CarModel implements Serializable {

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
    @SequenceGenerator(name = "sequenceGenerator")
    @Column(name = "id")
    private Long id;

    @Column(name = "name")
    private String name;

    ...
}

Сборка работает правильно, но приложение останавливается, когда я пытаюсь использовать Mapper. В нем говорится, что carModelService имеет значение null. Вот сгенерированный картографом класс реализации:

@Component
public class KitMapperImpl implements KitMapper {

    @Autowired // <-- this seems not working
    private CarModelService carModelService;

    @Override
    public Kit toEntity(KitDTO kitDTO) {
        if ( kitDTO == null ) {
            return null;
        }

        Kit kit = new Kit();

        kit.setSystemWeight( String.valueOf( kitDTO.getWeight() ) );
        kit.carModel( carModelService.findByName(kitDTO.getCarModel()) ); // <-- carModelService is null!

        // other setters

        return kit;
    }
}

Я пробовал много вещей, используя Decorator, @Context, выражение, внедрял класс @Mapper в класс @Service.

Я нашел много вопросов, но на самом деле мне никто не помог:

Mapstruct - Как я могу внедрить пружинную зависимость в класс Generated Mapper

Класс @Service не привязан автоматически в org.mapstruct. Класс @Mapper

MapStruct mapper не инициализируется с автоподключением при отладке

Любая помощь будет оценена по достоинству! Заранее спасибо!

Вы пробовали внедрение конструктора вместо внедрения поля? @Mapper (componentModel = «пружина», использует = CarModelService.class, инъекцияStrategy = InjectionStrategy.CONSTRUCTOR)

Vladimir Krylov 13.05.2022 12:26

Видимо все сделано правильно! Вы реализовали, как рекомендуемые документы структуры карты. Не могли бы вы поделиться своим pom.xml?

Renan Franca 13.05.2022 19:32

@VladimirKrylov Я пробовал, но не получилось.

Simone Colnaghi 16.05.2022 13:30
1
3
61
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Не могли бы вы поделиться сообщением об ошибке? Из информации, которой вы поделились, я вижу, что carModel в KitDto — это String, а в Entity — класс CarModel. Не уверен, как это реализовано в автоматически сгенерированном классе реализации mapstruct: kit.carModel( carModelService.findByName(kitDTO.getCarModel()) );.

Но я хотел бы поделиться другим подходом, не знаю, это лучшая практика или нет. В этом подходе вы можете создать абстрактный класс картографа, в котором вы можете вручную добавлять эти сопоставления в репозиторий @Autowired. Я поделился фрагментом для этого. Надеюсь, это поможет вам.

    @Mapper(componentModel = "spring", nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
public abstract class ProductMapper {
    @Autowired 
    private CarModelService carModelService;
    
    public abstract Kit convertDTOToEntity(KitDTO kitDTO);
    
       public Kit toEntity(KitDTO kitDTO);
        {   
        Kit kit = convertDTOToEntity(kitDTO);
        kit.setCarModel(carModelService.findByName(kitDTO.getCarModel()));
        return kit;
        }
    }

Любопытно узнать о других подходах, буду следить за этой темой. Можем обсудить лучшие практики

О том, как mapstruct использует carModelService.findByName внутри класса реализации. MapStruct автоматически пытается найти метод, который получает исходный тип в качестве параметра и возвращает целевой тип, а CarModelService был доступен в качестве опции с помощью «uses» над интерфейсом mapstruct ?

Renan Franca 13.05.2022 19:39

@ aakash-thomas Я попробовал предложенный вами подход, но не сработал. Сообщение об ошибке было Null Pointer Excpetion on carModelService. В любом случае, я нашел решение, я отправляю ответ как можно скорее :)

Simone Colnaghi 16.05.2022 13:09
Ответ принят как подходящий

Решение найдено!

Вместо того, чтобы напрямую вызывать метод Mapper toEntity() из класса @RestController, я внедрил преобразователь в класс CarModelService и создал метод, который вызывает этот преобразователь. Таким образом, поток:

Controller --> Service --> Mapper

@Service
@Transactional
public class KitService {
    private final KitRepository kitRepository;

    private final KitSearchRepository kitSearchRepository;

    private final KitMapper kitMapper; // <-- mapper declaration

    public KitService(KitRepository kitRepository, KitSearchRepository kitSearchRepository, KitMapper kitMapper) {
        this.kitRepository = kitRepository;
        this.kitSearchRepository = kitSearchRepository;
        this.kitMapper = kitMapper; // <-- mapper initilization
    }

    // here the method which calls mapper
    public Kit convertDTOToEntity(KitDTO kitDTO) {
        return kitMapper.toEntity(kitDTO);
    }

Таким образом, класс, сгенерированный Mapstruct, не выдает ошибку в CarModelService.

Кажется, что этот подход — единственный способ добиться этого, создать король «моста» между сервисами и картографами.

(Вы также можете использовать аннотацию @Autowired вместо конструктора)

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