Spring Boot多对一关联整合API出现评论重复问题求助
问题:Spring Boot多对一关联整合API中评论重复显示
使用Spring Boot开发Restful API,实现Comment与Category的多对一关联(多个Comment属于一个Category)。单独调用「获取所有分类」和「按分类ID获取评论」API时响应正确,但调用整合API时出现评论重复——非当前分类的评论也会显示在该分类结果中。
Category Entity
package com.mshakir.restfulwebservice.dto.entity; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.SequenceGenerator; import javax.persistence.Table; import javax.persistence.Transient; @Entity @Table(name = "categories") public class Category { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "category_generator") @SequenceGenerator(name = "category_generator", sequenceName = "category_sequence", initialValue = 20000, allocationSize = 1) @Column(name = "category_id") private Long id; @Column(name = "category_name", nullable = false, length = 32) private String categoryName; @Column(name = "category_desc", nullable = false, length = 64) private String categoryDescription; @Column(name = "is_published", nullable = false, length = 32) private boolean isPublished = true; @Transient private String publishedMessage; private String isActive; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getCategoryName() { return categoryName; } public void setCategoryName(String categoryName) { this.categoryName = categoryName; } public String getCategoryDescription() { return categoryDescription; } public void setCategoryDescription(String categoryDescription) { this.categoryDescription = categoryDescription; } public boolean getIsPublished() { return isPublished; } public void setIsPublished(boolean isPublished) { this.isPublished = isPublished; } public String getPublishedMessage() { String message = ""; if (isPublished) { message = "Category Id has been published to the Website"; } else { message = "Category Id has not been published to the Website"; } return message; } public String getIsActive() { return isActive; } public void setIsActive(String isActive) { this.isActive = isActive; } public void setPublishedMessage(String publishedMessage) { this.publishedMessage = publishedMessage; } @Override public String toString() { return "Category [id=" + id + ", categoryName=" + categoryName + ", categoryDescription=" + categoryDescription + " , isActive=" + isActive + "]"; } }
Comment Entity
package com.mshakir.restfulwebservice.dto.entity; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.JoinColumn; import javax.persistence.Lob; import javax.persistence.ManyToOne; import javax.persistence.SequenceGenerator; import javax.persistence.Table; import org.hibernate.annotations.OnDelete; import org.hibernate.annotations.OnDeleteAction; import com.fasterxml.jackson.annotation.JsonIgnore; @Entity @Table(name = "comments") public class Comment { @Id @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "comment_generator") @SequenceGenerator(name = "comment_generator", sequenceName = "comment_sequence", initialValue = 15001, allocationSize = 1) @Column(name = "comment_id") private Long id; @Lob @Column(name = "comment_content") private String content; @ManyToOne(fetch = FetchType.LAZY, optional = false) @JoinColumn(name = "category_id", nullable = false) @OnDelete(action = OnDeleteAction.CASCADE) @JsonIgnore private Category category; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public Category getCategory() { return category; } public void setCategory(Category category) { this.category = category; } @Override public String toString() { return "Comment [id=" + id + ", content=" + content + ", category=" + category + "]"; } }
Category Controller
@RestController @RequestMapping("/categories") public class CategoryController { public static Logger log = LoggerFactory.getLogger(CategoryController.class); @Autowired CategoryRepository categoryRepository; @Autowired CommentRepository commentRepository; private final CategoryServiceImpl categoryServiceImpl; @Autowired public CategoryController(CategoryServiceImpl categoryServiceImpl) { this.categoryServiceImpl = categoryServiceImpl; } @PostMapping("/create-new-category") public ResponseEntity<Category> createCategoryRecord(@RequestBody Category category) throws Exception { log.info("Category Controller: createCategoryRecord()"); Category createNewCategory = categoryServiceImpl.createNewCategory(category); HttpStatus statusCreated201 = HttpStatus.CREATED; return new ResponseEntity<Category>(createNewCategory, statusCreated201); } }
CategoryServiceImpl
@Service public class CategoryServiceImpl implements CategoryService { private CategoryRepository categoryRepository; @Autowired public CategoryServiceImpl(CategoryRepository categoryRepository) { this.categoryRepository = categoryRepository; } @Override public Category createNewCategory(Category category) { String categoryName = category.getCategoryName(); Optional<Category> findByCategoryName = categoryRepository.findByCategoryName(categoryName); if (findByCategoryName.isPresent()) { throw new InvalidDataException("Category already exists in the Database"); } Category newCategory = categoryRepository.save(category); return newCategory; } }
显示所有分类接口
@GetMapping("/show-all-categories") public ResponseEntity<List> showAllCategoriesAll(@RequestParam(required = false) String categoryName) { List listOfCategories = new ArrayList(); if (Utils.isEmptyString(categoryName)) { categoryRepository.findAll().forEach(category -> { listOfCategories.add(category); }); } else { List<Category> findByCategoryNameContaining = categoryRepository.findByCategoryNameContaining(categoryName); findByCategoryNameContaining.forEach(category -> listOfCategories.add(category)); } if (listOfCategories.isEmpty()) { return new ResponseEntity<List<Category>>(listOfCategories, HttpStatus.OK); } return new ResponseEntity<List<Category>>(listOfCategories, HttpStatus.OK); }
按分类ID获取评论接口
@GetMapping("/categories/{categoryId}/comments") public ResponseEntity<List> getAllCommentsByTutorialId(@PathVariable(value = "categoryId") Long categoryId) { if (!categoryRepository.existsById(categoryId)) { throw new ResourceNotFoundException("Not found Tutorial with id = " + categoryId); } List<Comment> comments = commentRepository.findByCategoryId(categoryId); List<Comment> findAll = commentRepository.findAll(); for (Comment comment: findAll) { System.out.println(comment.getCategory().getCategoryName()); System.out.println(comment.getCategory().getCategoryDescription()); } System.out.println(findAll); return new ResponseEntity<>(comments, HttpStatus.OK); }
整合API(问题代码)
@RestController @RequestMapping("/list-of-categories") public class ListOfCategory { @Autowired CategoryRepository categoryRepository; @Autowired CommentRepository commentRepository; @GetMapping("/show-all") public ResponseEntity<List<CategoryCommentResponse>> showAll() { List<CommentInfo> listOfComments = new ArrayList<CommentInfo>(); List<CategoryCommentResponse> listofAll = new ArrayList<CategoryCommentResponse>(); List<Category> categoryList = categoryRepository.findAll(); System.out.println(categoryList); for (Category category : categoryList) { CategoryCommentResponse response = new CategoryCommentResponse(); Long categoryId = category.getId(); response.setCategoryId(categoryId); response.setCategoryDescription(category.getCategoryDescription()); response.setCategoryName(category.getCategoryName()); List<Comment> listOfCommentsByCategoryId = commentRepository.findByCategoryId(categoryId); for (Comment comment : listOfCommentsByCategoryId) { CommentInfo commentInfo = new CommentInfo(); Long commentId = comment.getId(); commentInfo.setId(commentId); commentInfo.setContent(comment.getContent()); System.out.println(commentInfo); listOfComments.add(commentInfo); response.setComments(listOfComments); } listofAll.add(response); } return new ResponseEntity<List<CategoryCommentResponse>>(listofAll, HttpStatus.OK); } }
解决方案
问题根源
listOfComments变量定义在分类循环外部,每次处理新分类时未清空该列表,导致之前分类的评论被累积到当前分类的结果中,造成评论重复显示。
修改后的整合API代码
@RestController @RequestMapping("/list-of-categories") public class ListOfCategory { @Autowired CategoryRepository categoryRepository; @Autowired CommentRepository commentRepository; @GetMapping("/show-all") public ResponseEntity<List<CategoryCommentResponse>> showAll() { List<CategoryCommentResponse> listofAll = new ArrayList<CategoryCommentResponse>(); List<Category> categoryList = categoryRepository.findAll(); System.out.println(categoryList); for (Category category : categoryList) { CategoryCommentResponse response = new CategoryCommentResponse(); Long categoryId = category.getId(); response.setCategoryId(categoryId); response.setCategoryDescription(category.getCategoryDescription()); response.setCategoryName(category.getCategoryName()); // 将评论列表移到分类循环内部,确保每个分类拥有独立的评论集合 List<CommentInfo> listOfComments = new ArrayList<CommentInfo>(); List<Comment> listOfCommentsByCategoryId = commentRepository.findByCategoryId(categoryId); for (Comment comment : listOfCommentsByCategoryId) { CommentInfo commentInfo = new CommentInfo(); Long commentId = comment.getId(); commentInfo.setId(commentId); commentInfo.setContent(comment.getContent()); System.out.println(commentInfo); listOfComments.add(commentInfo); } // 遍历完当前分类的评论后再设置到响应对象中 response.setComments(listOfComments); listofAll.add(response); } return new ResponseEntity<List<CategoryCommentResponse>>(listofAll, HttpStatus.OK); } }
额外优化建议
解决N+1查询问题:当前代码会先查询所有分类,再逐个查询每个分类的评论,属于N+1查询。可通过以下方式优化:
- 在
Category实体中添加关联:@OneToMany(mappedBy = "category", fetch = FetchType.LAZY) private List<Comment> comments; - 在
CategoryRepository中添加批量查询方法:@Query("SELECT c FROM Category c JOIN FETCH c.comments") List<Category> findAllWithComments(); - 整合API中直接调用该方法,避免多次查询数据库。
- 在
清理冗余代码:按分类ID获取评论接口中的
commentRepository.findAll()循环属于调试代码,上线前建议删除。
内容的提问来源于stack exchange,提问作者Muhammad Shakir
相关产品推荐
相关产品推荐

