Spring Boot中文章点赞按钮的实现及状态识别问题
解决Spring Boot文章列表批量获取点赞状态的问题
这个问题我之前也遇到过,批量处理点赞状态确实是平衡性能和用户体验的关键,我给你分享几个实用的解决方案,既能避免N+1查询的性能问题,又能优雅地把点赞状态传递到Thymeleaf模板中:
核心思路
先一次性获取所有要展示的文章,再用单条SQL批量查询当前用户对这些文章的点赞记录,最后把点赞状态和文章关联起来,传递到前端渲染。
方案一:使用DTO封装文章+点赞状态
这种方式最直观,专门创建一个DTO类来承载文章信息和对应的点赞状态,代码可读性高。
1. 创建DTO类
public class ArticleLikeDTO { // 原文章实体 private Article article; // 当前用户是否点赞 private boolean isLiked; // 构造方法、getter、setter public ArticleLikeDTO(Article article, boolean isLiked) { this.article = article; this.isLiked = isLiked; } // getter和setter省略 }
2. Service层批量查询并封装DTO
@Service public class ArticleService { @Autowired private PeopleWhoLikedRepository likedRepo; @Autowired private CategoryRepository categoryRepo; public List<ArticleLikeDTO> getArticlesWithLikeStatus(Long categoryId, Long currentUserId) { // 1. 获取分类下的所有文章 Category category = categoryRepo.findById(categoryId) .orElseThrow(() -> new IllegalArgumentException("分类不存在")); List<Article> articles = category.getArticleList(); if (articles.isEmpty()) { return Collections.emptyList(); } // 2. 提取所有文章ID,用于批量查询 List<Long> articleIds = articles.stream() .map(Article::getId) .collect(Collectors.toList()); // 3. 单条SQL查询当前用户点赞的所有文章记录 List<PeopleWhoLiked> likedRecords = likedRepo.findByUserIdAndArticleIdIn(currentUserId, articleIds); // 4. 把点赞的文章ID存入Set,方便快速判断(O(1)查询) Set<Long> likedArticleIds = likedRecords.stream() .map(record -> record.getArticle().getId()) .collect(Collectors.toSet()); // 5. 封装成DTO列表 return articles.stream() .map(article -> new ArticleLikeDTO(article, likedArticleIds.contains(article.getId()))) .collect(Collectors.toList()); } }
3. Controller传递数据到Thymeleaf
@GetMapping("/category/{categoryId}") public String showCategoryArticles(@PathVariable Long categoryId, Model model, @AuthenticationPrincipal User currentUser) { // 获取当前用户ID(如果用户未登录,可以传null,后续处理) Long userId = currentUser != null ? currentUser.getId() : null; List<ArticleLikeDTO> articleList = articleService.getArticlesWithLikeStatus(categoryId, userId); model.addAttribute("articleList", articleList); return "articles/list"; }
4. Thymeleaf模板渲染
<div th:each="item : ${articleList}"> <h4 th:text="${item.article.title}"></h4> <p th:text="${item.article.content}"></p> <!-- 根据isLiked状态渲染按钮 --> <button th:if="${item.isLiked}" disabled class="liked-btn">已点赞</button> <button th:unless="${item.isLiked}" th:attr="data-article-id=${item.article.id}" class="like-btn">点赞</button> </div>
方案二:使用Map存储点赞状态(无需额外DTO)
如果不想创建新的DTO类,可以用一个Map<Long, Boolean>来存储文章ID和对应的点赞状态,同样高效。
1. Service层返回点赞状态Map
public Map<Long, Boolean> getUserLikeStatusForArticles(List<Article> articles, Long currentUserId) { if (articles.isEmpty() || currentUserId == null) { return Collections.emptyMap(); } List<Long> articleIds = articles.stream().map(Article::getId).collect(Collectors.toList()); List<PeopleWhoLiked> likedRecords = likedRepo.findByUserIdAndArticleIdIn(currentUserId, articleIds); Set<Long> likedIds = likedRecords.stream().map(r -> r.getArticle().getId()).collect(Collectors.toSet()); // 构建文章ID到点赞状态的Map return articles.stream() .collect(Collectors.toMap( Article::getId, article -> likedIds.contains(article.getId()) )); }
2. Controller传递文章列表和状态Map
@GetMapping("/category/{categoryId}") public String showCategoryArticles(@PathVariable Long categoryId, Model model, @AuthenticationPrincipal User currentUser) { Category category = categoryRepo.findById(categoryId).orElseThrow(() -> new IllegalArgumentException("分类不存在")); List<Article> articles = category.getArticleList(); Long userId = currentUser != null ? currentUser.getId() : null; Map<Long, Boolean> likeStatusMap = articleService.getUserLikeStatusForArticles(articles, userId); model.addAttribute("articles", articles); model.addAttribute("likeStatusMap", likeStatusMap); return "articles/list"; }
3. Thymeleaf模板使用Map判断状态
<div th:each="article : ${articles}"> <h4 th:text="${article.title}"></h4> <p th:text="${article.content}"></p> <button th:if="${likeStatusMap[article.id]}" disabled class="liked-btn">已点赞</button> <button th:unless="${likeStatusMap[article.id]}" th:attr="data-article-id=${article.id}" class="like-btn">点赞</button> </div>
额外注意事项
- 未登录用户处理:如果当前用户未登录,所有文章的点赞状态都设为
false,或者直接隐藏点赞按钮,避免空指针问题。 - 性能优化:用
Set存储已点赞的文章ID,因为Set.contains()是O(1)操作,比List.contains()的O(n)高效得多,尤其是文章数量较多时。 - JPA查询优化:确保
PeopleWhoLikedRepository的findByUserIdAndArticleIdIn方法生成的SQL是高效的,最好给user_id和article_id建立联合索引,进一步提升查询速度。
内容的提问来源于stack exchange,提问作者Mert Doe
相关产品推荐
相关产品推荐

