I need to inject a spring service class in the generated mapper implementation, so that I can use it via
@Mapping(target="x", expression="java(myservice.findById(id))")"
Is this applicable in Mapstruct-1.0?
I need to inject a spring service class in the generated mapper implementation, so that I can use it via
@Mapping(target="x", expression="java(myservice.findById(id))")"
Is this applicable in Mapstruct-1.0?
It should be possible if you declare Spring as the component model and add a reference to the type of myservice
:
@Mapper(componentModel="spring", uses=MyService.class)
public interface MyMapper { ... }
That mechanism is meant for providing access to other mapping methods to be called by generated code, but you should be able to use them in the expression that way, too. Just make sure you use the correct name of the generated field with the service reference.
@Context
and a handwritten method. –
Gherlein uses
works for me with a mapping like @Mapping(target="x", source="id")
instead of @Mapping(target="x", expression="java(myservice.findById(id))")"
–
Frisse As commented by brettanomyces, the service won't be injected if it is not used in mapping operations other than expressions.
The only way I found to this is :
I'm using CDI but it should be the samel with Spring :
@Mapper(
unmappedTargetPolicy = org.mapstruct.ReportingPolicy.IGNORE,
componentModel = "spring",
uses = {
// My other mappers...
})
public abstract class MyMapper {
@Autowired
protected MyService myService;
@Mappings({
@Mapping(target="x", expression="java(myservice.findById(obj.getId())))")
})
public abstract Dto myMappingMethod(Object obj);
}
It should be possible if you declare Spring as the component model and add a reference to the type of myservice
:
@Mapper(componentModel="spring", uses=MyService.class)
public interface MyMapper { ... }
That mechanism is meant for providing access to other mapping methods to be called by generated code, but you should be able to use them in the expression that way, too. Just make sure you use the correct name of the generated field with the service reference.
@Context
and a handwritten method. –
Gherlein uses
works for me with a mapping like @Mapping(target="x", source="id")
instead of @Mapping(target="x", expression="java(myservice.findById(id))")"
–
Frisse What's worth to add in addition to the answers above is that there is more clean way to use spring service in mapstruct mapper, that fits more into "separation of concerns" design concept that avoids mixing mappers and spring beans, called "qualifier". Easy re-usability in other mappers as a bonus. For sake of simplicity I prefer named qualifier as noted here http://mapstruct.org/documentation/stable/reference/html/#selection-based-on-qualifiers Example would be:
import org.mapstruct.Mapper;
import org.mapstruct.Named;
import org.springframework.stereotype.Component;
@Component
public class EventTimeQualifier {
private EventTimeFactory eventTimeFactory; // ---> this is the service you want yo use
public EventTimeQualifier(EventTimeFactory eventTimeFactory) {
this.eventTimeFactory = eventTimeFactory;
}
@Named("stringToEventTime")
public EventTime stringToEventTime(String time) {
return eventTimeFactory.fromString(time);
}
}
This is how you use it in your mapper:
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
@Mapper(componentModel = "spring", uses = EventTimeQualifier.class)
public interface EventMapper {
@Mapping(source = "checkpointTime", target = "eventTime", qualifiedByName = "stringToEventTime")
Event map(EventDTO eventDTO);
}
@Mapper
annotation at the EventTimeQualifier
does not seem to be necessary. It worked for me without it because of the componentModel="spring"
. –
Frimaire Since 1.2 this can be solved with a combination of @AfterMapping and @Context.. Like this:
@Mapper(componentModel="spring")
public interface MyMapper {
@Mapping(target="x",ignore = true)
// other mappings
Target map( Source source, @Context MyService service);
@AfterMapping
default void map( @MappingTarget Target.X target, Source.ID source, @Context MyService service) {
target.set( service.findById( source.getId() ) );
}
}
The service can be passed as context.
A nicer solution would be to use an @Context
class which wrap MyService
in stead of passing MyService
directly. An @AfterMapping
method can be implemented on this "context" class: void map( @MappingTarget Target.X target, Source.ID source )
keeping the mapping logic clear of lookup logic. Checkout this example in the MapStruct example repository.
@MappingTarget
? –
Cindiecindra map()
is getting called before the builders build()
method. –
Veronicaveronika I am using Mapstruct 1.3.1 and I have found this problem is easy to solve using a decorator.
Example:
@Mapper(unmappedTargetPolicy = org.mapstruct.ReportingPolicy.IGNORE,
componentModel = "spring")
@DecoratedWith(FooMapperDecorator.class)
public interface FooMapper {
FooDTO map(Foo foo);
}
public abstract class FooMapperDecorator implements FooMapper{
@Autowired
@Qualifier("delegate")
private FooMapper delegate;
@Autowired
private MyBean myBean;
@Override
public FooDTO map(Foo foo) {
FooDTO fooDTO = delegate.map(foo);
fooDTO.setBar(myBean.getBar(foo.getBarId());
return fooDTO;
}
}
Mapstruct will generate 2 classes and mark the FooMapper that extends FooMapperDecorator as the @Primary bean.
@DecoratedWith
carefully. You inject the generated mapper (i.e. delegate
) and apply it first. Then, perform your customizations in the decorator method body. –
Lucillelucina @Service
. In the FooMapperDecorator the @Autowired
of that is not working. The myBean is null :( Does anyone know why? –
Nominal I went through all the responses in this question but was not able to get things working. I dug a little further and was able to solve this very easily. All you need to do is to make sure that:
@Mapper(componentModel = "spring") public abstract class MyMapper { @Autowired protected AppProperties appProperties; @Mapping(target = "account", source = "request.account") @Mapping(target = "departmentId", source = "request.departmentId") @Mapping(target = "source", source = ".", qualifiedByName = "mapSource") public abstract MyDestinationClass getDestinationClass(MySourceClass request); @Named("mapSource") String mapSource(MySourceClass request) { return appProperties.getSource(); } }
Also, remember, that your mapper is now a spring bean. You will need to inject it your calling class as follows:
private final MyMapper myMapper;
Since mapstruct 1.5.0 you can use a constant for spring componentmodel generation
@Mapper(
uses = {
//Other mappings..
},
componentModel = MappingConstants.ComponentModel.SPRING)
I can't use componentModel="spring"
because I work in a large project that doesn't use it. Many mappers includes my mapper with Mappers.getMapper(FamilyBasePersonMapper.class)
, this instance is not the Spring bean and the @Autowired
field in my mapper is null.
I can't modifiy all mappers that use my mapper. And I can't use particular constructor with the injections or the Spring's @Autowired
dependency injection.
The solution that I found: Using a Spring bean instance without using Spring directly:
Here is the Spring Component that regist itself first instance (the Spring instance):
@Component
@Mapper
public class PermamentAddressMapper {
@Autowired
private TypeAddressRepository typeRepository;
@Autowired
private PersonAddressRepository personAddressRepository;
static protected PermamentAddressMapper FIRST_INSTANCE;
public PermamentAddressMapper() {
if(FIRST_INSTANCE == null) {
FIRST_INSTANCE = this;
}
}
public static PermamentAddressMapper getFirstInstance(){
return FIRST_INSTANCE;
}
public static AddressDTO idPersonToPermamentAddress(Integer idPerson) {
//...
}
//...
}
Here is the Mapper that use the Spring Bean accross getFirstInstance
method:
@Mapper(uses = { NationalityMapper.class, CountryMapper.class, DocumentTypeMapper.class })
public interface FamilyBasePersonMapper {
static FamilyBasePersonMapper INSTANCE = Mappers.getMapper(FamilyBasePersonMapper.class);
@Named("idPersonToPermamentAddress")
default AddressDTO idPersonToPermamentAddress(Integer idPerson) {
return PermamentAddressMapper.getFirstInstance()
.idPersonToPermamentAddress(idPersona);
}
@Mapping(
source = "idPerson",
target="permamentAddres",
qualifiedByName="idPersonToPermamentAddress" )
@Mapping(
source = "idPerson",
target = "idPerson")
FamilyDTO toFamily(PersonBase person);
//...
Maybe this is not the best solution. But it has helped to decrement the impact of changes in the final resolution.
in my case my mapper was no importing List.class in the autogenerated file so I added (imports = List.class)
, ie:
@Mapper(imports = List.class)
public interface MyMapper {
[...]
}
It's very simple:
@Mapper(componentModel = "spring")
public abstract class SimpleMapper {
@Autowired
protected Myservice service;
}
The most important is:
scanBasePackages
.Ref: mapstruct
© 2022 - 2024 — McMap. All rights reserved.