Home > Blockchain >  Spring Boot Entity to DTO ConverterNotFoundException
Spring Boot Entity to DTO ConverterNotFoundException

Time:04-22

I have a simple API, built with Spring Boot, where I am trying to convert entity classes to the corresponding dtos.

Student Entity

@Entity(name = "students")
@AllArgsConstructor
@Getter
@Setter
public class Student extends AbstractUpdatable<Long> {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "student_id")
    private Long id;

    @Column(name = "student_first_name", nullable = false)
    @NotNull(message = "First name is mandatory")
    private String firstName;

    @Column(name = "student_last_name", nullable = false)
    @NotNull(message = "Last name is mandatory")
    private String lastName;

    @Column(name = "student_ssn", unique = true, nullable = false)
    @NotNull(message = "SSN is mandatory")
    @Size(min = 10, max = 10)
    private String ssn;

    @Column(name = "student_age")
    @Min(5)
    @Max(100)
    private Integer studentAge;

    @Column(name = "student_email")
    @Email
    private String email;

    @Column(name = "student_level")
    private Integer studentLevel; // TODO could be enum or separate entity

    @Column(name = "student_creation_date")
    private Date creationDate; // TODO check spring's feature to manage creation and update dates (auditing)

    @ManyToOne
    @JoinColumn(name = "group_id")
    private Group group;

    public Student() {
        this.creationDate = new java.util.Date(); // TODO this will be removed when spring's auditing is utilized
    }

}

Group Entity

@Entity(name = "groups")
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
public class Group extends AbstractUpdatable<Long> {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "group_id")
    private Long id;

    @Column(name = "group_name")
    private String Name;

    @OneToMany(mappedBy = "group")
    @JsonIgnore
    private List<Student> students;

}

Student DTO

public class StudentDto extends AbstractStudentDto implements Serializable {

    private final Long id;

    private final Date creationDate;

    public StudentDto(String firstName, String lastName, String email, Long id, GroupDto group, Integer studentAge, Integer studentLevel,
                      Date creationDate) {
        this.id = id;
        this.firstName = firstName;
        this.lastName = lastName;
        this.email = email;
        this.group = group;
        this.studentAge = studentAge;
        this.studentLevel = studentLevel;
        this.creationDate = creationDate;
    }

    public Long getId() {
        return id;
    }

    public Date getCreationDate() {
        return creationDate;
    }
}

Group DTO

public class GroupDto extends AbstractGroupDto{

    private final Long id;

    public GroupDto(Long id, String name, List<StudentDto> students) {
        this.id = id;
        this.name = name;
        this.students = students;
    }

    public Long getId() {
        return id;
    }
}

GroupToGroupDtoConverter

@Component
public class GroupToGroupDtoConverter implements org.springframework.core.convert.converter.Converter<Group, GroupDto> {

    private final ConversionService conversionService;

    @Autowired
    public GroupToGroupDtoConverter(ConversionService conversionService) {
        this.conversionService = conversionService;
    }

    @Override
    public GroupDto convert(Group source) {

        var convertedStudents = new ArrayList<StudentDto>();

        if (!CollectionUtils.isEmpty(source.getStudents())) {
            source.getStudents().forEach(student ->
                    convertedStudents.add(conversionService
                            .convert(student, StudentDto.class)));
        }

        return new GroupDto(source.getId(), source.getName(), convertedStudents);
    }
}

And a very similar StudentToStudentDtoConverter.

The issue is that when the code needs to do the conversion from any of the entities to their dtos I get

org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [com.studentmanagement.model.Group] to type [com.studentmanagement.dto.group.GroupDto]

Now if I try to remove the conversion of the students' list to a list of student dtos in the converter above, so the converter looks like this:

@Component
    public class GroupToGroupDtoConverter implements org.springframework.core.convert.converter.Converter<Group, GroupDto> {
    
        @Override
        public GroupDto convert(Group source) {
            return new GroupDto(source.getId(), source.getName(), new ArrayList<StudentDto>());
        }

    }

The conversion works with no issues (with a dummy students list of course). Am I missing something when I am adding the conversion service inside my converters?

CodePudding user response:

I think the problem is ConversionService can't convert your classes by default. Instead try to inject a class implementing ConversionService with correct convert method implementation inside.

CodePudding user response:

I tried to replicate the issue and did a small working demo on this.

One thing I found while doing that, that could be relevant to your case, is that injecting a conversion service into a converter is not trivial (see e.g. this and this relevant issues).

Also, important as well, do not forget to register the converters, as shown below on the code samples, and e.g. here. From the error message you posted, seems like the service cannot find the needed converter.

For my demo, please note I removed the group field from the StudentDTO class to simplify things. I hope it helps, happy to share the full code as well in github.

I used the following converter for Group:

@Component
public class GroupToGroupDtoConverter implements org.springframework.core.convert.converter.Converter<Group, GroupDto> {

    private ConversionService conversionService;

    public GroupToGroupDtoConverter(ConversionService conversionService) {
        this.conversionService = conversionService;
    }

    @Override
    public GroupDto convert(Group group) {


        List<StudentDto> studentDtoList =
                group.getStudents().stream().map(a -> conversionService.convert(a, StudentDto.class)).collect(Collectors.toList());
        GroupDto groupDto = new GroupDto(group.getId(), group.getName(), studentDtoList);

        return groupDto;
    }
}

But in order to successfully inject the conversion service and register the converters I added this:

@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    @Lazy
    ConversionService conversionService;

    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverter(new GroupToGroupDtoConverter(conversionService));
        registry.addConverter(new StudentToStudentDtoConvervter());

    }
}

If, for example, I comment out the first addConverter line, I get the Converter not found exception:

org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [com.example.conversion_entities.Group] to type [com.example.conversion_entities.GroupDto]

Once all is done, the following test passes:

@RunWith(SpringRunner.class)
@SpringBootTest
class GroupToGroupDtoConverterTest {


    @Autowired
    ConversionService conversionService;


    @Test
    void convert() {
        Student studentA = new Student();
        studentA.setFirstName("John");
        studentA.setLastName("Doe");

        Student studentB = new Student();
        studentB.setFirstName("Jane");
        studentB.setLastName("Doe");

        List<Student> studentList = new ArrayList<>();
        studentList.add(studentA);
        studentList.add(studentB);

        Group group = new Group(1L, "groupA", studentList);

        GroupDto convertedGroupDto = conversionService.convert(group, GroupDto.class);
        assertEquals("John", convertedGroupDto.getStudents().get(0).getFirstName());
        assertEquals("Jane", convertedGroupDto.getStudents().get(1).getFirstName());
    }
}
  • Related