javaspring-bootinsert-updatemodelmapperservice-layer

ModelMapper DTO-->Entity. How to skip unconditionally all fields not mapped


I have two classes (entity and DTO)

public class Deliver  {

    private Long id;
    private String uri;
    private Instant moment;
    private DeliverStatus status; // enum PENDING,ACCEPTED,REJECTED
    private String feedback;      // feedback about received task
    private Integer correctCount; // nr of correct questions
    private Enrollment enrollment;
    private Lesson lesson;

        // constructors, getters and setters..

public class DeliverRevisionDto  {

    private DeliverStatus status;
    private String feedback;
    private Integer correctCount;
    
    // constructors, getters and setters..

The goal is pretty simple, update the entity fields conveyed by Dto class I have the following code at Service layer (Spring Boot version 2.4.4):

@Service
public class DeliverService {

    @Autowired
    private DeliverRepository deliverRepository;
    
    @Autowired
    private ModelMapper modelMapper;
    
    @Transactional
    public void saveRevision(Long id, DeliverRevisionDto dto) {
    
        Deliver deliver = deliverRepository.getOne(id);
        
        System.out.println("BEFORE MAPPING: " + deliver.toString());  // # debug purpose

        deliver = modelMapper.map(dto, Deliver.class);
 
        // # debug purpose
        TypeMap<DeliverRevisionDto, Deliver> tm = modelMapper.getTypeMap(DeliverRevisionDto.class, Deliver.class);
        List<Mapping> list = tm.getMappings();
        for (Mapping m : list)
        {
            System.out.println(m);
        }
        System.out.println("AFTER MAPPING: " + deliver.toString()); // # debug purpose
        deliverRepository.save(deliver);
    }

}

The console output is:

BEFORE MAPPING: Deliver [id=1, uri=``https://github/someone.com``, moment=2020-12-10T10:00:00Z, status=PENDING, feedback=null, correctCount=null, enrollment=com.devsuperior.dslearnbds.entities.Enrollment@7e0, lesson=com.devsuperior.dslearnbds.entities.Task@23]`
`PropertyMapping[DeliverRevisionDto.correctCount -> Deliver.correctCount]`
`PropertyMapping[DeliverRevisionDto.feedback -> Deliver.feedback]`
`PropertyMapping[DeliverRevisionDto.status -> Deliver.status]`
`AFTER MAPPING: Deliver [id=null, uri=null, moment=null, status=ACCEPTED, feedback=Muito bem cabra, tarefa aceita., correctCount=5, enrollment=null, lesson=null]

The mapping of the 3 fields in DTO is done correctly, BUT all the other fields of my entity are set to null. I know that I can skip fields according http://modelmapper.org/user-manual/property-mapping/

The problem is that I don´t want to couple the code with specific field names/getters/setters, that´s the reason I´m using ModelMapper. I wonder if there is any configuration that, upon mapping the modelmapper object says "Hey, the TARGET class have way more fields than the SOURCE class, I will left them untouched unconditionally (meaning I don´t need to say what fields are).

I'm trying to map fields between 2 classes with different set of fields (some are the same), and when I map the class with smaller set of fields to the one with bigger set of fields, the mapper set fields that don´t match with "null", I want these fields untouched (with original values) without I telling which one they are, after all, the mapper knows which ones match.


Solution

  • ModelMapper documentation is not the best part of that framework. Let us see what happens in your code.

    Here you fetch the entity to be updated from the repo:

    Deliver deliver = deliverRepository.getOne(id);
    

    and log it having all the fields as should be. However this line:

    deliver = modelMapper.map(dto, Deliver.class);
    

    does a re-assignment to your variable deliver. This method creates a new instance of Deliver class and assigns it to variable deliver so discarding the entity fetched from repo.

    This new instance will have all the fields that are not existing or not set in DTO null.

    This is the API doc that my IDE provides, fotr these two different methods:

    String org.modelmapper.ModelMapper.map(Object source, Class destinationType) Maps source to an instance of destinationType. Mapping is performed according to the corresponding TypeMap. If no TypeMap exists for source.getClass() and destinationType then one is created.

    Versus

    void org.modelmapper.ModelMapper.map(Object source, Object destination)
    Maps source to destination. Mapping is performed according to the corresponding TypeMap. If no TypeMap exists for source.getClass() and destination.getClass() then one is created.

    It might not be clearly stated that the first method actually creates a new instance based on the type (Class) passed but it should be clear that ModelMapper cannot alter some arbitrary variable just by knowing the type. You need to pass the variable to alter as method parameter.