Reputation: 131
Given the Source class as defined below:
class Person{
private String name;
private int age;
@OneToMany(fetch = FetchType.LAZY, mappedBy = "peroson")
List<Phone> phones
// getters and setters
}
and the Phone class as defined below:
class Phone{
private Long id;
private String phoneNumber;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "person")
private Person person;
// getters and setters
}
and DTO objects:
class PersonDTO{
private String name;
private int age;
List<PhoneDTO> phones
// getters and setters
}
class PhoneDTO{
private Long id;
private String phoneNumber;
private PersonDTO person;
// getters and setters
}
I have an interface :
@Mapper(uses={PersonMapper.class})
interface PhoneMapper{
PhoneDTO toDto(Phone source);
Phone toEntity(PhoneDTO source);
}
@Mapper(uses={PhoneMapper.class})
interface PersonMapper{
PersonDTO toDto(Person source);
Person toEntity(PersonDTO source);
}
when I use PeronMapper
or PhoneMapper
there is a recursive call, because PersonMapper
use PhoneMapper
and PersonMapper
use PhoneMapper
.
how to solve that?
Upvotes: 1
Views: 2500
Reputation: 180
Sometimes the OneToMany child class reference should reflect the same name in the parent Dto. Example
class PersonDTO{
private String name;
private int age;
List<PhoneDTO> phones
}
this phones name should match exactly in the entity name. And you needs to add Eagar loading as fetch type
@OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
@Fetch(value=FetchMode.SELECT)
List<Phone> phones
Upvotes: 0
Reputation: 21393
There are numerous ways to avoid the recursion.
First option:
Avoid by explicitly ignoring in one of your mappers
Second option:
Use the @Context
and write your own memoization. There is an example mapstruct-mapping-with-cycles in the mapstruct examples repo.
The idea is to have a @Context
public class CycleAvoidingMappingContext {
private Map<Object, Object> knownInstances = new IdentityHashMap<Object, Object>();
@BeforeMapping
public <T> T getMappedInstance(Object source, @TargetType Class<T> targetType) {
return (T) knownInstances.get( source );
}
@BeforeMapping
public void storeMappedInstance(Object source, @MappingTarget Object target) {
knownInstances.put( source, target );
}
}
And then pass in an instance of this context to your mappers:
@Mapper(uses={PersonMapper.class})
interface PhoneMapper{
PhoneDTO toDto(Phone source, @Context CycleAvoidingContext context);
Phone toEntity(PhoneDTO source, @Context CycleAvoidingContext context);
}
@Mapper(uses={PhoneMapper.class})
interface PersonMapper{
PersonDTO toDto(Person source, @Context CycleAvoidingContext context);
Person toEntity(PersonDTO source, @Context CycleAvoidingContext context);
}
Of course the CycleAvoidingMappingContext
can have more specified sources, instead of just Object
. You can have one for Person
and PersonDTO
only for example. It is up to you how you want to implement it.
Edit:
When you have cyclic dependency between Mappers then the only solution is to use a dependency injection framework. The default MapStruct Mappers
factory does not support cycles.
Upvotes: 1