You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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);

}
}

额外优化建议

  1. 解决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中直接调用该方法,避免多次查询数据库。
  2. 清理冗余代码:按分类ID获取评论接口中的commentRepository.findAll()循环属于调试代码,上线前建议删除。


内容的提问来源于stack exchange,提问作者Muhammad Shakir

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 00:25:28