'MapStruct mapping OneToMany collection - not create entity from null id

I have Student class with @OneToMany relationship to classes like below

public class Student {

    @Id
    @GeneratedValue
    private Long id;

    //fields

    @ManyToOne(fetch=FetchType.EAGER)
    @JoinColumn(name = "class_id")
    private Class classEntity;

And class entity:

public class Class {

    @Id
    @GeneratedValue
    private Long id;

    //fields
   
    @EqualsAndHashCode.Exclude
    @ToString.Exclude
    @OneToMany(mappedBy = "classEntity", cascade = CascadeType.PERSIST, fetch = FetchType.EAGER)
    private Set<Student> students;

I'm using MapStruct to convert dto to entity and entity to dto:

StudentMapper.java

@Mapper(componentModel = "spring", uses = {ClassMapper.class})
public interface StudentMapper extends EntityMapper<StudentDTO, Student> {

    @Mapping(source = "classEntity.id", target="classId")
    @Mapping(source = "classEntity.name", target="className")
    StudentDTO toDto(Student student);

    @Mapping(source = "classId", target="classEntity.id")
    Student toEntity(StudentDTO studentDTO);


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

    default Long toId(Student student) {
        if (student == null){
            return null;
        }
        return student.getId();
    }

ClassMapper.java

@Mapper(componentModel = "spring")
public interface ClassMapper extends EntityMapper<ClassDTO, Class> {

    @Mapping(target = "educatorId" , source = "educator.id")
    ClassDTO toDto(Class classEntity);

    @Mapping(target = "educator.id" , source = "educatorId")
    Class toEntity(ClassDTO classDTO);

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

    default Long toId(Class classEntity) {
        if (classEntity == null){
            return null;
        }
        return classEntity.getId();
    }

}

These mappers extends simple EntityMapper interface

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);

    Set <E> toEntity (Set<D> dtoSet);

    Set<D> toDto (Set<E> entitySet);

Generated StudentMapperImpl.class

public Student toEntity(StudentDTO studentDTO) {
        if ( studentDTO == null ) {
            return null;
        }

        StudentBuilder student = Student.builder();

        student.classEntity( studentDTOToClass( studentDTO ) );
        //fields
        return student.build();
    }

 protected Class studentDTOToClass(StudentDTO studentDTO) {
        if ( studentDTO == null ) {
            return null;
        }

        Class class1 = new Class();

        class1.setId( studentDTO.getClassId() );

        return class1;
    }

Everything works fine when I'm trying to convert studentDTO to student entity with filled classId in studentDTO class. The problem starts when classId in studentDTO is null. Then mapper create new Class Objects and saving it into database....

And don't want this because not every student will have assigned class. I've tried everything with CascadeType in Student.class but I think the problem is in my MapStruct mapper.

Eventually I can write my own mappers without mapstruct but I'd rather avoid it. Please help me.



Solution 1:[1]

In your StudentMapper you've made a mistake with the @Mapping annotation. In the ClassMapper you go from Id to Class while in the @Mapping annotation you go from Id to Class.Id.

For nested mappings mapstruct will attempt to create the object(s) containing the nested fields. Which causes the problem you're experiencing.

If you change it as shown below it should use the method in the ClassMapper for converting from Id into Class.

    @Mapping(source = "classId", target="classEntity")
    Student toEntity(StudentDTO studentDTO);

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Ben Zegveld