Spring项目中基于多对多实现点赞及Answer实体点赞数统计问询
实现用户单次点赞回答+回答点赞数统计的Spring方案
嘿,这个需求太常见了!我来给你一步步拆解怎么在Spring项目里实现用户单次点赞回答,同时在Answer实体里维护点赞统计数的功能。咱们从实体映射开始,再到业务逻辑,最后聊聊高并发场景下的优化点。
1. 实体类配置(以JPA为例)
首先咱们把三个表对应的实体类配置好,重点是给Answer实体新增likeCount字段,同时处理好关联关系。
User实体
@Entity @Table(name = "USER") public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private String name; // 可选:关联用户的所有点赞记录(双向关联) @OneToMany(mappedBy = "user", fetch = FetchType.LAZY) private Set<UserAnswerLike> userAnswerLikes = new HashSet<>(); // getter、setter、构造器省略 }
Answer实体(核心:新增likeCount字段)
@Entity @Table(name = "ANSWER") public class Answer { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer id; private String message; // 新增的点赞统计字段,默认初始为0 private Integer likeCount = 0; // 关联该回答的所有点赞记录 @OneToMany(mappedBy = "answer", fetch = FetchType.LAZY) private Set<UserAnswerLike> userAnswerLikes = new HashSet<>(); // getter、setter、构造器省略 }
UserAnswerLike实体(中间关联表)
这个表是用户和回答的关联表,用来实现“单次点赞”的约束,咱们用嵌入式主键来处理联合主键:
@Entity @Table(name = "user_answer_likes", uniqueConstraints = @UniqueConstraint(columnNames = {"user_id", "answer_id"})) // 强制一个用户只能给同一回答点一次赞 public class UserAnswerLike { @EmbeddedId private UserAnswerLikeId id; @ManyToOne(fetch = FetchType.LAZY) @MapsId("userId") // 映射主键中的userId字段 @JoinColumn(name = "user_id") private User user; @ManyToOne(fetch = FetchType.LAZY) @MapsId("answerId") // 映射主键中的answerId字段 @JoinColumn(name = "answer_id") private Answer answer; // 嵌入式主键类,必须实现Serializable @Embeddable public static class UserAnswerLikeId implements Serializable { private Integer userId; private Integer answerId; // 必须重写equals和hashCode,否则JPA无法识别主键 @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; UserAnswerLikeId that = (UserAnswerLikeId) o; return Objects.equals(userId, that.userId) && Objects.equals(answerId, that.answerId); } @Override public int hashCode() { return Objects.hash(userId, answerId); } // getter、setter、构造器省略 } // getter、setter、构造器省略 }
2. 点赞/取消点赞的业务逻辑实现
接下来咱们实现核心的点赞切换逻辑,先定义Repository接口,再写Service层代码。
Repository接口
首先需要三个Repository,其中UserAnswerLikeRepository需要自定义方法来检查点赞状态和删除点赞记录:
// UserRepository和AnswerRepository直接继承JpaRepository即可 public interface UserAnswerLikeRepository extends JpaRepository<UserAnswerLike, UserAnswerLike.UserAnswerLikeId> { // 检查用户是否已经点赞过该回答 boolean existsByUserIdAndAnswerId(Integer userId, Integer answerId); // 根据用户ID和回答ID删除点赞记录 void deleteByUserIdAndAnswerId(Integer userId, Integer answerId); }
Service层实现
在Service里处理点赞/取消点赞的逻辑,同时更新Answer的likeCount:
@Service @Transactional public class AnswerLikeService { private final UserAnswerLikeRepository userAnswerLikeRepository; private final AnswerRepository answerRepository; // 构造器注入(推荐) public AnswerLikeService(UserAnswerLikeRepository userAnswerLikeRepository, AnswerRepository answerRepository) { this.userAnswerLikeRepository = userAnswerLikeRepository; this.answerRepository = answerRepository; } // 点赞/取消点赞切换方法 public void toggleLike(Integer userId, Integer answerId) { boolean hasLiked = userAnswerLikeRepository.existsByUserIdAndAnswerId(userId, answerId); Answer answer = answerRepository.findById(answerId) .orElseThrow(() -> new RuntimeException("目标回答不存在")); if (hasLiked) { // 已点赞,执行取消操作 userAnswerLikeRepository.deleteByUserIdAndAnswerId(userId, answerId); answer.setLikeCount(Math.max(answer.getLikeCount() - 1, 0)); // 确保点赞数不会变成负数 } else { // 未点赞,执行点赞操作 UserAnswerLike likeRecord = new UserAnswerLike(); UserAnswerLike.UserAnswerLikeId likeId = new UserAnswerLike.UserAnswerLikeId(); likeId.setUserId(userId); likeId.setAnswerId(answerId); likeRecord.setId(likeId); // 可选:关联User和Answer实体(如果需要的话) // likeRecord.setUser(userRepository.findById(userId).orElseThrow(...)); // likeRecord.setAnswer(answer); userAnswerLikeRepository.save(likeRecord); answer.setLikeCount(answer.getLikeCount() + 1); } answerRepository.save(answer); } }
3. 高并发场景优化建议
如果你的项目有高并发点赞的场景,上面的基础实现可能会有线程安全问题,这里给两个优化方向:
原子更新点赞数
直接用数据库的原子操作来更新点赞数,避免多线程下的计数错误。可以在AnswerRepository里定义自定义SQL:
public interface AnswerRepository extends JpaRepository<Answer, Integer> { @Modifying @Query("UPDATE Answer a SET a.likeCount = a.likeCount + 1 WHERE a.id = :answerId") int incrementLikeCount(@Param("answerId") Integer answerId); @Modifying @Query("UPDATE Answer a SET a.likeCount = a.likeCount - 1 WHERE a.id = :answerId AND a.likeCount > 0") int decrementLikeCount(@Param("answerId") Integer answerId); }
然后修改Service里的逻辑,调用这两个方法代替直接设置likeCount,这样数据库会保证操作的原子性。
缓存优化
如果点赞数的访问频率很高,可以用Redis缓存Answer的点赞数。点赞/取消点赞时,先更新缓存,再异步更新数据库;或者更新数据库后失效缓存,下次查询时从数据库加载并写入缓存,减少数据库压力。
内容的提问来源于stack exchange,提问作者user226335
相关产品推荐
相关产品推荐

