Spring Rest API:调用getDoctorByName时Doctor的categories返回空数组
Spring Rest API查询医生时categories字段返回空数组问题排查与修复
问题描述
调用GET接口getDoctorByName查询医生信息时,除categories字段外,医生其他信息均正常返回,但categories始终返回空数组。医生记录创建、分类创建及为医生添加分类的功能均正常,数据库中已存在关联数据。
相关代码
Doctor实体类
@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实体类
@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; }
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 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 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控制器
@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>> getAllDoctors() { 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控制器
@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)); } }
问题原因及修复方案
1. 双向关联导致循环序列化
Category实体中的doctors集合与Doctor实体中的categories集合形成双向关联,Jackson序列化Doctor对象时会触发循环引用,最终导致categories字段被自动置为空数组。
修复: 在Category实体的doctors字段上添加@JsonIgnore注解,阻止Jackson序列化该反向关联:
@ManyToMany(mappedBy = "categories", fetch = FetchType.LAZY) @JsonIgnore private Set<Doctor> doctors;
2. findAll()方法未加载关联数据
在getDoctorByName方法的分支中,当未传入fname和lname时会调用doctorRepository.findAll(),但该方法未配置@EntityGraph,即使Doctor实体的categories设置为FetchType.EAGER,JPA默认的findAll()查询也可能不会主动加载关联数据。
修复: 在DoctorRepository中覆盖findAll()方法并添加@EntityGraph:
@Repository public interface DoctorRepository extends JpaRepository<Doctor, Long> { // 原有方法... @Override @EntityGraph(attributePaths = "categories") List<Doctor> findAll(); }
可选优化
getAllDoctors()中手动调用doctor.getCategories().size()触发懒加载的方式可以移除,因为添加了带@EntityGraph的findAll()方法后,关联数据会被一次性加载,性能更优:
public List<Doctor> getAllDoctors() { return doctorRepository.findAll(); }
内容的提问来源于stack exchange,提问作者Timo4ka
相关产品推荐
相关产品推荐

