13

i have an object school that has an object person, persons are already saved in a data base and when i save a school object i give it a person id

so in the class school i have an attribute person of type Person, and in SchoolDTO i have an attribute personId of type Long

 @Mapper(componentModel = "spring", uses = { PersonMapper.class }) 
 public interface SchoolMapper extends EntityMapper<SchoolDTO, School>{ 

  @Mapping(source = "personId", target = "person") 
  School toEntity(SchoolDTO schoolDTO); 
 } 

 School school = schoolMapper.toEntity(schoolDTO); 
 log.info(school.getPerson()); 


public interface EntityMapper <D, E> {

 E toEntity(D dto);

 D toDto(E entity);

 List <E> toEntity(List<D> dtoList);

 List <D> toDto(List<E> entityList);
}


@Mapper(componentModel = "spring", uses = {})
public interface PersonMapper extends EntityMapper<PersonDTO, Person> {

  default Person fromId(Long id) {
   if (id == null) {
     return null;
   }
   Person person= new Person();
   person.setId(id);
   return person;
  }
}

the problem here when i display the person it shows me the id with their value and the other attribute null

kapex
  • 28,903
  • 6
  • 107
  • 121
Aymen Kanzari
  • 1,765
  • 7
  • 41
  • 73

2 Answers2

22

The reason why your Person is displayed only with the id value set is because your fromId method creates an empty Person and sets only the id.

I presume you want to fetch the Person from the database.

To achieve this you just need to tell MapStruct to use a service, or you can inject it in your mapper and perform the fetch.

If you have a service like:

public interface PersonService {

    Person findById(Long id);
}

And your mapper:

 @Mapper(componentModel = "spring", uses = { PersonService.class }) 
 public interface SchoolMapper extends EntityMapper<SchoolDTO, School>{ 

  @Mapping(source = "personId", target = "person") 
  School toEntity(SchoolDTO schoolDTO); 
 } 
Filip
  • 19,269
  • 7
  • 51
  • 60
  • How does it know which service method to call? – gjw80 Jan 24 '22 at 19:53
  • I cannot say "how", but it really works for me. I suppose `componentModel = "spring"` enables spring magic – Kotodid Mar 09 '22 at 14:18
  • 3
    The why it works is because MapStruct looks in all the methods of defined services from Mapper#uses. In this particular case it will look for a method to map a Long into a Person. That's why it uses that method – Filip Mar 09 '22 at 18:14
  • 1
    Is there a way how to do it if source and target are collections? i.e. ``List`` as source and ``List`` as target. – kism3t Jun 29 '23 at 12:00
7

We can generalize the previous answer by introducing a ReferenceMapper, like this:

@Component
public class ReferenceMapper {

    @PersistenceContext
    private EntityManager entityManager;

    @ObjectFactory
    public <T> T map(@NonNull final Long id,  @TargetType Class<T> type) {
        return entityManager.getReference(type, id);
    }
}

Then, PersonMapper would be:

@Mapper(componentModel = "spring", uses = {ReferenceMapper.class})
public interface PersonMapper {

    Person toEntity(Long id);
}

And finally SchoolMapper:

@Mapper(componentModel = "spring",uses = {PersonMapper.class})
public interface SchoolMapper {

  @Mapping(source = "personId", target = "person") 
  School toEntity(SchoolDTO schoolDTO); 
}

And generated source would be:

@Override
    public School toEntity(InDto dto) {
        if ( dto == null ) {
            return null;
        }

        School school = new School();

        school.setPerson( personMapper.toEntity( dto.getPersonId() ) );
        // other setters

        return school;
    }
Andreas Gelever
  • 1,736
  • 3
  • 19
  • 25
  • thank you, this helps a newbie like me a lot – Nguyen DN Nov 27 '21 at 00:55
  • This example is much better @Andreas. Just one change - You don't need `Person toEntity(Long id);` in PersonMapper. Cause it generates toPerson(PersonDto dto) { ... person.setId(referenceMapper.map(dto.getId(), Long.class); ... Which will fail to resolve the entity as there's no entity with type "Long". Remove it ans use the RefereceMapper directly in the SchoolMapper. Tested in Mapstruct 1.5.x – Ketan Dhamasana Dec 27 '22 at 17:26