0

I am working on a project for my class. Need to map DTO to Entity and vice versa. My DTO does not have an Id field and conversion is happening at Controller Layer. 2 other classes are extending my entity, thus @Superbuilder.

My conversion Entity -> DTO works fine with ModelMapper

But DTO -> Entity does not. The commented line in convertToEntity I thought should work, but nope kept getting the infamous ' has non-private no-argument constructor '. Spent hours trying to fix. Decided to go with a simpler solution, though really wanted to make ModelMapper work.

If any of you could help me find a fix, I would really appreciate it. Thanks in advance for your time and responses.

======My DTO=====

@Value
@Jacksonized
@Builder
@AllArgsConstructor
public class UserMetaData {

    @NotNull @NotBlank @NotEmpty(message = "First name cannot be empty!")
    @JsonProperty("firstName")
    String firstName;
    @NotNull @NotBlank @NotEmpty(message = "Last name cannot be empty!")
    @JsonProperty("lastName")
    String lastName;
    @Max(value = 110, message = "Invalid age!") @Min(value = 16, message = "Must be older than 16!")
    @JsonProperty("age")
    int age;
    @NotNull
    @JsonProperty("privilege")
    Privilege privilege;
    //@NotNull
    @JsonProperty("alias")
    String alias;
    //@Singular
    @JsonProperty("groups")
    Set<Group> groups;


}

=====My Entity=====

@Document(collection = "Users")
@SuperBuilder
@Data
@RequiredArgsConstructor(onConstructor = @__(@PersistenceConstructor))
public class Member implements User {

@Id
protected final UUID id;
protected final String firstName;
protected final String lastName;
protected final int age;
@Builder.Default
protected final Privilege privilege = NONE;
protected String alias;
protected Set<Group> groups;

protected Set<Group> addGroup(Group group) {
    this.groups.add(group);
    return this.groups;
}

}

=====Bean for ModelMapper=====

 @Bean
public ModelMapper modelMapper() {
    ModelMapper modelMapper = new ModelMapper();
    modelMapper.getConfiguration()
            .setFieldMatchingEnabled(true)
            .setFieldAccessLevel(org.modelmapper.config.Configuration.AccessLevel.PRIVATE);
    return modelMapper;
}

=====Converters=====

 private UserMetaData convertToDTO(Member member) {
    return modelMapper.map(member, UserMetaData.UserMetaDataBuilder.class)
            .build();
}

private Member convertToEntity(UserMetaData dto) {
//        return modelMapper.map(dto, Member.MemberBuilder.class).id(UUID.randomUUID()).build();
    return Member.builder()
            .id(UUID.randomUUID())
            .firstName(dto.getFirstName())
            .lastName(dto.getLastName())
            .age(dto.getAge())
            .build();
}
TheNope
  • 1
  • 1
  • 3

1 Answers1

1

Your DTO class is using @Builder while your Entity class is using @SuperBuilder.
There are difference between @Builder and @SuperBuilder. Look at target files after you compiled project:

  • @Builder will create a static class named DTOBuilder and it has a public constructor
public class DTO {
    //...
    public static class DTOBuilder {
         public DTOBuilder() {
         }
    }
}
  • @SuperBuilder will create a public abstract class EntityBuilder and a private implemented class EntityBuilderImpl with a private constructor
public class Entity {
     //...
     public abstract static class EntityBuilder<...> {
     }
     //...
     private static final class EntityBuilderImpl extends EntityBuilder {
            private EntityBuilderImpl() {
            }
     }
}

So, ModelMapper requires target class should be public class and public contructor. That's why you can not map to EntityBuilder but it's ok with DTOBuilder.


Solution is, instead of mapping to EntityBuilder, you should map to Entity directly. NOTE that your entity must have No Args Constructor


Add @NoArgsConstructor into your Entity and convert from your DTO to Entity class instead of EntityBuilder

//...
@NoArgsConstructor
public class Member implements User {
//...
}

private Member convertToEntity(UserMetaData dto) {
    return modelMapper.map(dto, Member.class);
}