如何编写JPA查询方法实现按点赞数倒序展示书评?
按点赞数倒序展示书评的实现方案
当前代码里的findAllByOrderByHeartDesc()无法实现按点赞数排序,因为heart是BookReview关联的集合字段,JPA无法直接基于集合进行排序,必须基于点赞的统计数量来处理。以下是几种可行的实现方案:
方案一:新增点赞数字段(推荐,性能最优)
在BookReview实体中维护一个点赞数字段,每次点赞/取消点赞时更新该字段,排序和查询效率最高。
步骤1:修改BookReview实体
@Entity public class BookReview extends Timestamped { // 原有字段保持不变... @Column(columnDefinition = "int default 0") private Integer heartCount = 0; // 默认初始点赞数为0 // 新增getter、setter及辅助方法 public Integer getHeartCount() { return heartCount; } public void setHeartCount(Integer heartCount) { this.heartCount = heartCount; } // 点赞时调用 public void incrementHeartCount() { this.heartCount += 1; } // 取消点赞时调用 public void decrementHeartCount() { if (this.heartCount > 0) { this.heartCount -= 1; } } }
步骤2:更新点赞/取消点赞逻辑
在处理点赞请求时,保存Heart实体后调用bookReview.incrementHeartCount()并更新BookReview;取消点赞时调用decrementHeartCount()并更新BookReview,确保字段与实际点赞数同步。
步骤3:修改Repository查询方法
在BookReviewRepository中定义排序查询方法:
List<BookReview> findAllByOrderByHeartCountDesc();
步骤4:优化getHome方法
无需再调用heartRepository统计数量,直接使用实体中维护的字段,减少数据库查询:
public ResponseDto<?> getHome() { List<BookReview> bookReviews = bookReviewRepository.findAllByOrderByHeartCountDesc(); List<HomeResponseDto> responseDtoList = new ArrayList<>(); for (BookReview review : bookReviews) { responseDtoList.add(HomeResponseDto.builder() .id(review.getId()) .username(review.getMember().getUsername()) .thumbnail(review.getThumbnail()) .title(review.getTitle()) .author(review.getAuthor()) .publisher(review.getPublisher()) .review(review.getReview()) .heart(review.getHeartCount()) .createdAt(review.getCreatedAt()) .modifiedAt(review.getModifiedAt()) .build() ); } return ResponseDto.success(responseDtoList); }
方案二:用JPQL统计排序(无需修改实体)
如果不想新增实体字段,可以通过JPQL关联Heart表统计点赞数并排序。
步骤1:在BookReviewRepository中定义JPQL查询
@Query("SELECT br, COUNT(h) as heartCount FROM BookReview br LEFT JOIN br.heart h GROUP BY br.id ORDER BY heartCount DESC") List<Object[]> findAllWithHeartCountOrderByDesc();
步骤2:修改getHome方法
处理查询返回的结果数组,提取BookReview和对应的点赞数:
public ResponseDto<?> getHome() { List<Object[]> results = bookReviewRepository.findAllWithHeartCountOrderByDesc(); List<HomeResponseDto> responseDtoList = new ArrayList<>(); for (Object[] result : results) { BookReview review = (BookReview) result[0]; Long heartCount = (Long) result[1]; responseDtoList.add(HomeResponseDto.builder() .id(review.getId()) .username(review.getMember().getUsername()) .thumbnail(review.getThumbnail()) .title(review.getTitle()) .author(review.getAuthor()) .publisher(review.getPublisher()) .review(review.getReview()) .heart(heartCount) .createdAt(review.getCreatedAt()) .modifiedAt(review.getModifiedAt()) .build() ); } return ResponseDto.success(responseDtoList); }
方案三:用Spring Data JPA Projection(优雅的DTO查询)
通过定义Projection接口,直接查询所需字段和点赞数,减少数据传输,代码更简洁。
步骤1:定义Projection接口
public interface BookReviewWithHeartCount { Long getId(); String getUsername(); String getThumbnail(); String getTitle(); String getAuthor(); String getPublisher(); String getReview(); Long getHeart(); LocalDateTime getCreatedAt(); LocalDateTime getModifiedAt(); }
步骤2:在BookReviewRepository中定义查询
@Query("SELECT br.id as id, m.username as username, br.thumbnail as thumbnail, br.title as title, br.author as author, br.publisher as publisher, br.review as review, COUNT(h) as heart, br.createdAt as createdAt, br.modifiedAt as modifiedAt FROM BookReview br LEFT JOIN br.member m LEFT JOIN br.heart h GROUP BY br.id ORDER BY heart DESC") List<BookReviewWithHeartCount> findAllWithHeartCountProjection();
步骤3:修改getHome方法
通过流式转换将Projection转换为HomeResponseDto:
import java.util.stream.Collectors; public ResponseDto<?> getHome() { List<BookReviewWithHeartCount> projections = bookReviewRepository.findAllWithHeartCountProjection(); List<HomeResponseDto> responseDtoList = projections.stream() .map(proj -> HomeResponseDto.builder() .id(proj.getId()) .username(proj.getUsername()) .thumbnail(proj.getThumbnail()) .title(proj.getTitle()) .author(proj.getAuthor()) .publisher(proj.getPublisher()) .review(proj.getReview()) .heart(proj.getHeart()) .createdAt(proj.getCreatedAt()) .modifiedAt(proj.getModifiedAt()) .build()) .collect(Collectors.toList()); return ResponseDto.success(responseDtoList); }
方案对比
- 方案一:性能最优,适合点赞频繁、数据量大的场景,但需要维护额外字段。
- 方案二:无需修改实体,快速实现,但每次查询都要关联统计,数据量大时性能下降。
- 方案三:代码优雅,减少不必要的数据传输,适合复杂DTO场景,同样依赖关联统计。
内容的提问来源于stack exchange,提问作者sejin park
相关产品推荐
相关产品推荐

