-1

I have such a problem that when calling the GET (getDoctorByName) method for the doctor, I get all the information about the doctor except for the categories field (there are entries in it, but it returns me an empty array). Creating doctor records and categories work and adding a category for a doctor too. Created data in the database: Category table Doctor table Doctor Category table

What do I need to change or what did I do wrong?

Doctor Entity:

@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table(name = "doctor")
public class Doctor {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String email;
    private String password;
    @Column(name = "first_name")
    private String fname;
    @Column(name = "last_name")
    private String lname;
    private String description;
    private String number;
    @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    @JoinTable(
            name = "doctor_category",
            joinColumns = @JoinColumn(name = "doctor_id"),
            inverseJoinColumns = @JoinColumn(name = "category_id")
    )
    private Set<Category> categories;
}

Category Entity:

@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table(name = "category")
public class Category {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    @ManyToMany(mappedBy = "categories", fetch = FetchType.LAZY)
    private Set<Doctor> doctors;
}

Category and Doctor Repository:

@Repository
public interface CategoryRepository extends JpaRepository<Category, Long> {
    List<Category> findByName(String name);
}

@Repository
public interface DoctorRepository extends JpaRepository<Doctor, Long> {
    @EntityGraph(attributePaths = "categories")
    List<Doctor> findByFnameAndLname(String fname, String lname);
    @EntityGraph(attributePaths = "categories")
    List<Doctor> findByFname(String fname);
    @EntityGraph(attributePaths = "categories")
    List<Doctor> findByLname(String lname);
}

Category Service:

@Service
public class CategoryService {
    @Autowired
    private CategoryRepository categoryRepository;

    public Category createCategory(Category category) {
        return categoryRepository.save(category);
    }

    public Category editCategory(Long categoryId, Category myCategory) {
        Category category = categoryRepository.findById(categoryId)
                .orElseThrow(() -> new ResourceNotFoundException("Категория с id: " + categoryId + " не найден"));
        category.setName(myCategory.getName());
        categoryRepository.save(category);
        return category;
    }

    public List<Category> getCategoryByName(String name) {
        List<Category> categories;
        if (name != null) {
            categories = categoryRepository.findByName(name);
        } else categories = categoryRepository.findAll();
        return categories;
    }

    public HttpStatus deleteCategory(Long categoryId) {
        categoryRepository.deleteById(categoryId);
        return HttpStatus.OK;
    }

}

Doctor Service:

@Service
public class DoctorService {
    @Autowired
    private DoctorRepository doctorRepository;
    @Autowired
    private CategoryRepository categoryRepository;

    public Doctor createDoctor(Doctor doctor) {
        return doctorRepository.save(doctor);
    }

    public Doctor editDoctor(Long doctorId, Doctor myDoctor) {
        Doctor doctor = doctorRepository.findById(doctorId)
                .orElseThrow(() -> new ResourceNotFoundException("Доктор с id: " + doctorId + " не найден"));
        doctor.setEmail(myDoctor.getEmail());
        doctor.setPassword(myDoctor.getPassword());
        doctor.setFname(myDoctor.getFname());
        doctor.setLname(myDoctor.getLname());
        doctor.setDescription(myDoctor.getDescription());
        doctor.setNumber(myDoctor.getNumber());
        return doctorRepository.save(doctor);
    }

    public Doctor addCategoryForDoctor(Long doctorId, Long categoryId) {
        Doctor doctor = doctorRepository.findById(doctorId)
                .orElseThrow(() -> new ResourceNotFoundException("Доктор с id: " + doctorId + " не найден"));
        Category category = categoryRepository.findById(categoryId)
                .orElseThrow(() -> new ResourceNotFoundException("Категория с id: " + categoryId + " не найден"));
        Set<Category> categories = doctor.getCategories();
        categories.add(category);
        doctor.setCategories(categories);
        return doctorRepository.save(doctor);
    }

    public List<Doctor> getAllDoctors() {
        List<Doctor> doctors = doctorRepository.findAll();
        for (Doctor doctor : doctors) {
            // вызовите getCategories() у каждого доктора, чтобы загрузить связанные категории
            doctor.getCategories().size();
        }
        return doctors;
    }

    public List<Doctor> getDoctorByName(String fname, String lname) {
        List<Doctor> doctors;
        if (fname != null && lname != null) {
            doctors = doctorRepository.findByFnameAndLname(fname, lname);
        } else if (fname != null) {
            doctors = doctorRepository.findByFname(fname);
        } else if (lname != null) {
            doctors = doctorRepository.findByLname(lname);
        } else {
            doctors = doctorRepository.findAll();
        }
        return doctors;
    }

    public HttpStatus deleteDoctor(Long doctorId) {
        doctorRepository.deleteById(doctorId);
        return HttpStatus.OK;
    }
}

Doctor Controller:

@RestController
@RequestMapping("/doctor")
public class DoctorController {
    @Autowired
    private DoctorService doctorService;

    @PostMapping
    public ResponseEntity<Doctor> createDoctor(@RequestBody Doctor doctor) {
        return ResponseEntity.ok(doctorService.createDoctor(doctor));
    }

    @PutMapping
    public ResponseEntity<Doctor> editDoctor(@RequestParam Long doctorId, @RequestBody Doctor doctor) {
        return ResponseEntity.ok(doctorService.editDoctor(doctorId, doctor));
    }

    @PutMapping("/add/category")
    public ResponseEntity<Doctor> addCategoryForDoctor(@RequestParam Long doctorId, @RequestParam Long categoryId) {
        return ResponseEntity.ok(doctorService.addCategoryForDoctor(doctorId, categoryId));
    }

    @GetMapping("/all")
    public ResponseEntity<List<Doctor>> get() {
        return ResponseEntity.ok(doctorService.getAllDoctors());
    }

    @GetMapping
    public ResponseEntity<List<Doctor>> getDoctorByName(@RequestParam(required = false) String fname,
                                                        @RequestParam(required = false) String lname) {
        return ResponseEntity.ok(doctorService.getDoctorByName(fname, lname));
    }

    @DeleteMapping
    public ResponseEntity<HttpStatus> deleteDoctor(@RequestParam Long doctorId) {
        return ResponseEntity.ok(doctorService.deleteDoctor(doctorId));
    }
}

Category Controller:

@RestController
@RequestMapping("/category")
public class CategoryController {
    @Autowired
    private CategoryService categoryService;

    @PostMapping
    public ResponseEntity<Category> createCategory(@RequestBody Category category) {
        return ResponseEntity.ok(categoryService.createCategory(category));
    }

    @PutMapping
    public ResponseEntity<Category> editCategory(@RequestParam Long categoryId, @RequestBody Category category) {
        return ResponseEntity.ok(categoryService.editCategory(categoryId, category));
    }

    @GetMapping
    public ResponseEntity<List<Category>> getCategoryByName(@RequestParam(required = false) String name) {
        return ResponseEntity.ok(categoryService.getCategoryByName(name));
    }

    @DeleteMapping
    public ResponseEntity<HttpStatus> deleteCategory(@RequestBody Long categoryId) {
        return ResponseEntity.ok(categoryService.deleteCategory(categoryId));
    }
}
Timo4ka
  • 23
  • 7
  • Please don't insert screenshots but the source code as text (using the format provided). It helps us to copy/paste parts of when working on the solution. – Mar-Z Mar 18 '23 at 14:00
  • In `Doctor Entity` class change `FetchType.Lazy` to `FetchType.Eager`. It's not recommended to use your entity class in the controller. Use at the service layer only and use POJO classes for the controller, which prevent you from calling recursion calls from Doctor -> Category -> Doctor... – Dhaval Gajjar Mar 18 '23 at 14:05

2 Answers2

0

In the entity Doctor you have defined fetch type lazy for the collection of categories. It means that the collection is not loaded from the database until you access it in code.

Solutions

  1. change fetch type to eager or
  2. in the service class call getCategories.size() for all doctors
  3. in the repository you can use the EntityGraph annotation like:

(Please mind the use of {} because attributePaths is an array)

@EntityGraph(attributePaths = {"categories"})
List<Doctor> findByLname(String lname);
Mar-Z
  • 2,660
  • 2
  • 4
  • 16
  • 1
    It didn't help to issue the same: [ { "id": 1, "email": "example@mail.com", "password": "secretKey", "fname": "Bob", "lname": "Lname", "description": "Some text", "number": "777777777", "categories": [] } ] – Timo4ka Mar 18 '23 at 14:42
  • 1
    I have added another solution, please check it. – Mar-Z Mar 18 '23 at 15:56
0

One solution to your problem could be to change FetchType.LAZY to FetchType.EAGER in the Doctor class for categories. You can find more information on this here.

inside 'Doctor Entity' change this

@ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)

into this

@ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)

Another way to solve the issue is to replace the @Data annotation with @Getter and @Setter annotations on both Category and Doctor classes.

Moreover, it's not considered a good practice to pass entities through the controller. It's better to use DTOs instead. You can read more about it here.

Here are examples of DTO classes for Category and Doctor:

DoctorDTO

@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
public class DoctorDTO {
    private Long id;
    private String email;
    private String password;
    private String fname;
    private String lname;
    private String description;
    private String number;
    private List<CategoryDTO> categories;


    public static DoctorDTO convertEntityToDto(Doctor doctor) {
        DoctorDTO dto = new DoctorDTO();

        dto.setId(doctor.getId());
        dto.setEmail(doctor.getEmail());
        dto.setPassword(doctor.getPassword());
        dto.setFname(doctor.getFname());
        dto.setLname(doctor.getLname());
        dto.setDescription(doctor.getDescription());
        dto.setNumber(doctor.getNumber());
        dto.setCategories(CategoryDTO.convertEntitySetToDTOList(doctor.getCategories()));

        return dto;
    }

    public static List<DoctorDTO> convertEntityListToDTOList(List<Doctor> doctors) {
        return doctors.stream().map(DoctorDTO::convertEntityToDto).collect(Collectors.toList());
    }

    public static List<DoctorDTO> convertEntitySetToDTOList(Set<Doctor> doctors) {
        return doctors.stream().map(DoctorDTO::convertEntityToDto).collect(Collectors.toList());
    }

    public static Set<CategoryDTO> convertEntitySetToDTOSet(Set<Category> doctors) {
        return doctors.stream().map(CategoryDTO::convertEntityToDto).collect(Collectors.toSet());
    }

    public static Page<DoctorDTO> convertEntityPageToDTOPage(Page<Doctor> doctors) {
        return new PageImpl<>(convertEntityListToDTOList(doctors.getContent()), doctors.getPageable(), doctors.getTotalElements());
    }
}

CategoryDTO

@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
public class CategoryDTO {
    private Long id;
    private String name;

    public static CategoryDTO convertEntityToDto(Category category) {
        CategoryDTO dto = new CategoryDTO();

        dto.setId(category.getId());
        dto.setName(category.getName());

        return dto;
    }

    public static List<CategoryDTO> convertEntityListToDTOList(List<Category> categories) {
        return categories.stream().map(CategoryDTO::convertEntityToDto).collect(Collectors.toList());
    }

    public static List<CategoryDTO> convertEntitySetToDTOList(Set<Category> categories) {
        return categories.stream().map(CategoryDTO::convertEntityToDto).collect(Collectors.toList());
    }

    public static Set<CategoryDTO> convertEntitySetToDTOSet(Set<Category> categories) {
        return categories.stream().map(CategoryDTO::convertEntityToDto).collect(Collectors.toSet());
    }

    public static Page<CategoryDTO> convertEntityPageToDTOPage(Page<Category> categories) {
        return new PageImpl<>(convertEntityListToDTOList(categories.getContent()), categories.getPageable(), categories.getTotalElements());
    }
}

Also, change the 'get' function in DoctorController to use DTOs. My advice is to do this for all other functions as well so that communication with the outside world is done through DTOs instead of entities.

@GetMapping("/all")
public ResponseEntity<List<DoctorDTO>> get() {
    List<DoctorDTO> doctorDTOS = DoctorDTO.convertEntityListToDTOList(doctorService.getAllDoctors());
    return ResponseEntity.ok(doctorDTOS);
}

Finally, modify the getAllDoctors function in DoctorService to look like this:

@Transactional
public List<Doctor> getAllDoctors() {
    List<Doctor> doctors = doctorRepository.findAll();
    for (Doctor doctor : doctors) {
        // вызовите getCategories() у каждого доктора, чтобы загрузить связанные категории
        Hibernate.initialize(doctor.getCategories());
        int size = doctor.getCategories().size();
        System.out.println("size: " + size);
    }
    return doctors;
}
  • It didn't help to issue the same: [ { "id": 1, "email": "example@mail.com", "password": "secretKey", "fname": "Bob", "lname": "Lname", "description": "Some text", "number": "777777777", "categories": [] } ] – Timo4ka Mar 18 '23 at 14:42
  • please replace the images with the code so that I can try it on my side. – Danijel Sudar Mar 18 '23 at 15:13
  • Replaced everything with the text of the code – Timo4ka Mar 18 '23 at 15:30
  • Hi, thanks for the help, I first tried to add the methods that you wrote to me, but there was Data in the classes. At that time, these methods still did not output data in the categories field. As soon as I replaced Data with Getter and Setter, everything worked. Thank you so much <3 – Timo4ka Mar 19 '23 at 08:31