I'm new to Mapstruct and I'm trying to understand it properly.
What I want to achieve is converting from a DTO String parameter (carModel) to his Entity, retrieve using Service and Repository.
The problem is that Mapper class generated by Mapstruct is trying to inject the Service class with @Autowired
annotation, but it's not working. The service is null.
Here's my @Mapper
class:
@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);
}
The @Service
class:
@Service
@Transactional
public class CarModelService {
private final CarModelRepository carModelRepository;
@Transactional(readOnly = true)
public CarModel findByName(String name) {
return carModelRepository.findByName(name).orElse(null);
}
}
The @Repository
class:
@Repository
public interface CarModelRepository extends JpaRepository<CarModel, Long> {
Optional<CarModel> findByName(String carModelName);
}
The DTO and Entity classes:
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;
...
}
The build work properly but the application stop when I try to use the Mapper. It says that carModelService is null. Here's the mapper generated implementation class:
@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;
}
}
I've tried many things, using Decorator, @Context, expression, inject the @Mapper class into the @Service class.
I've found many questions but actually no one helped me:
Mapstruct - How can I inject a spring dependency in the Generated Mapper class
@Service Class Not Autowired in org.mapstruct.@Mapper Class
MapStruct mapper not initialized with autowired when debug
Any help would be appreciated! Thanks in advance!
CodePudding user response:
Can you please share the error message?
From the information that you shared, I can see the carModel in KitDto is String and in Entity is CarModel class. Not sure how mapstruct's auto generated implementation class implemented this: kit.carModel( carModelService.findByName(kitDTO.getCarModel()) );
.
But I would like to share another approach,Don't know this is a best practice or not. In this approach you can create a abstarct class of mapper, in which you can @Autowired repository can manually add those mapping. I shared the snippet for it. Hopefully this will help you.
@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;
}
}
Curious about the other approaches, will follow this thread. We can discuss the best practices
CodePudding user response:
Found the solution!
Instead of calling directly the Mapper method toEntity()
from the @RestController
class, I injected the mapper in the CarModelService
class and created a method that call the mapper.
In this way the flow is:
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);
}
In this way, the generated class by Mapstruct doesn't give error on the CarModelService.
Seems like this approach is the only way to achieve this, create a king of "bridge" between services and mappers.
(You can use also the @Autowired
annotation instead of constructor)