Search code examples
lombokmapstructdefault-constructorobjectfactory

mapstruct and lombok: How to avoid usage of no args constructor in generated mapper?


I want mapstruct to not use a no-args-constructor even though it exists in my DTOs and Entities (as far as I know jsonb, jpa, jaxb, ... usually require a no-args-constructor):

@RequiredArgsConstructor
@NoArgsConstructor(access = PROTECTED)
@Getter
@Accessors(fluent = true)
public class DepartmentDTO
{
    @NonNull @Setter private String name;

    public DepartmentDTO(@NonNull DepartmentEntity department, @NonNull MapStructContext context)
    {
        this(department.name());
        log.debug("context {}", context);
    }
}

The according DepartmentEntity class is implemented analoguously.

In the mapper interface I implemented methods which are annotated with @ObjectFactory and call a custom constructor with parameters in the mapper interface itself, but mapstruct still uses the no-args-constructor:

@Mapper
public interface MapStructMapper
{
    MapStructMapper INSTANCE = Mappers.getMapper(MapStructMapper.class);

    DepartmentEntity map(DepartmentDTO    department, MapStructContext context);
    DepartmentDTO    map(DepartmentEntity department, MapStructContext context);

    @ObjectFactory
    default DepartmentDTO create(
            @NonNull DepartmentEntity department, @NonNull @Context MapStructContext context)
    {
        return new DepartmentDTO(department, context);
    }


    @ObjectFactory
    default DepartmentEntity create(
            @NonNull DepartmentDTO department, @NonNull @Context MapStructContext context)
    {
        return new DepartmentEntity(department, context);
    }

    @ToString
    class MapStructContext
    {
        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 );
        }
    }

}

The context parameter is used for proper handling of cyclic dependencies in my custom constructors. Here is a part of the generated code:

  public DepartmentEntity map(DepartmentDTO department, MapStructMapper.MapStructContext context) {
    if (department == null && context == null) {
      return null;
    } else {
      DepartmentEntity departmentEntity = new DepartmentEntity();
      return departmentEntity;
    }
  }

  public DepartmentDTO map(DepartmentEntity department, MapStructMapper.MapStructContext context) {
    if (department == null && context == null) {
      return null;
    } else {
      DepartmentDTO departmentDTO = new DepartmentDTO();
      return departmentDTO;
    }
  }

What am I missing?

By the way: There is a lombok-generated setter in the DTO and in the Entity (fluent-style). Why is it not called in the generated mapper?


Solution

  • Turned out that I mistakenly thought that @Default is only there to resolve multiple constructors ambiguities. In fact adding @Default to a parameterised constructor lets mapstruct prefer that constructor over an existing no-args-constructor.