为JPA实体实现单用户单帖子仅可加入一次的收藏列表功能
解决同一用户重复收藏同一帖子的问题
我现有Post和User实体(一个用户可发布多篇帖子/广告),已通过WatchlistPost实体实现收藏列表功能,但目前存在同一用户可多次收藏同一帖子的问题,需要实现:用户可收藏多篇不同帖子,但同一帖子仅能被同一用户收藏一次。
现有代码参考
Post实体
@Entity @Getter @Setter @NoArgsConstructor @AllArgsConstructor public class Post { private long EXPIRATION_TIME_DAYS = 14; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @NotBlank(message = "Required") @Size(min=20, message = "Minimum of 20 characters") @Size(max=100, message = "Maximum of 100 characters") @Column(unique = true) private String title; @NotBlank(message = "Required") @Size(min = 50, message = "Minimum of 50 characters") @Size(max = 2000, message = "Maximum of 2000 characters") @Column(length = 2000) private String description; // 省略其他字段 @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name="user_id") @OnDelete(action = OnDeleteAction.CASCADE) @JsonIgnore private User user; }
User实体
@Entity @Getter @Setter @NoArgsConstructor @AllArgsConstructor public class User { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @NotBlank(message = "Required") private String fullName; // 省略其他字段 @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "user", orphanRemoval = true) @OnDelete(action = OnDeleteAction.CASCADE) private List<Post> posts = new ArrayList<>(); }
WatchlistPost实体
package com.carspot.app.entity; import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import org.hibernate.annotations.OnDelete; import org.hibernate.annotations.OnDeleteAction; import javax.persistence.*; @Entity @Table(name="watchlist_post") @Data @AllArgsConstructor @NoArgsConstructor public class WatchlistPost { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @OneToOne(fetch = FetchType.LAZY) @OnDelete(action = OnDeleteAction.CASCADE) @JsonIgnore @JoinColumn(name = "post_id", referencedColumnName = "id") private Post post; @ManyToOne(fetch = FetchType.LAZY) @OnDelete(action = OnDeleteAction.CASCADE) @JsonIgnore @JoinColumn(name = "user_id", referencedColumnName = "id") private User user; }
原添加收藏Service方法
@Override public void addToWatchlist(Long postId, String emailAddress) { User user = userRepository.findUserByEmailAddress(emailAddress); Post post = findPostById(postId); WatchlistPost watchlistPost = new WatchlistPost(); watchlistPost.setPost(post); watchlistPost.setUser(user); postWatchlistRepository.save(watchlistPost); }
原添加收藏Controller方法
@PostMapping("api/post/watchlist/{postId}") public ResponseEntity<?> addToWatchList(@PathVariable Long postId, Principal principal) { postService.addToWatchlist(postId, principal.getName()); return new ResponseEntity<>("Post added to watchlist", HttpStatus.OK); }
解决方案
1. 修复实体类关联错误
你的WatchlistPost中Post字段用了@OneToOne是错误的——一篇帖子可以被多个用户收藏,必须改成@ManyToOne,否则会导致其他用户无法收藏同一帖子:
@Entity @Table(name="watchlist_post") @Data @AllArgsConstructor @NoArgsConstructor public class WatchlistPost { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; // 修正为@ManyToOne @ManyToOne(fetch = FetchType.LAZY) @OnDelete(action = OnDeleteAction.CASCADE) @JsonIgnore @JoinColumn(name = "post_id", referencedColumnName = "id") private Post post; @ManyToOne(fetch = FetchType.LAZY) @OnDelete(action = OnDeleteAction.CASCADE) @JsonIgnore @JoinColumn(name = "user_id", referencedColumnName = "id") private User user; }
2. 数据库层面添加联合唯一约束
从底层阻止重复数据插入,给watchlist_post表的user_id和post_id添加联合唯一索引:
ALTER TABLE watchlist_post ADD CONSTRAINT uk_user_post UNIQUE (user_id, post_id);
如果是新建表,可直接在DDL中指定:
CREATE TABLE watchlist_post ( id BIGINT AUTO_INCREMENT PRIMARY KEY, post_id BIGINT, user_id BIGINT, FOREIGN KEY (post_id) REFERENCES post(id) ON DELETE CASCADE, FOREIGN KEY (user_id) REFERENCES user(id) ON DELETE CASCADE, UNIQUE KEY uk_user_post (user_id, post_id) );
3. 实体类配置唯一约束(可选)
让JPA生成表时自动带上联合唯一约束,在@Table注解中添加配置:
@Entity @Table(name="watchlist_post", uniqueConstraints = @UniqueConstraint(columnNames = {"user_id", "post_id"})) @Data @AllArgsConstructor @NoArgsConstructor public class WatchlistPost { // 字段保留修改后的内容 }
4. 业务层添加前置校验
在保存收藏记录前,先查询该用户是否已收藏过该帖子,避免触发数据库异常:
@Override public void addToWatchlist(Long postId, String emailAddress) { User user = userRepository.findUserByEmailAddress(emailAddress); Post post = findPostById(postId); // 检查是否已存在收藏记录 boolean exists = postWatchlistRepository.existsByUserAndPost(user, post); if (exists) { throw new RuntimeException("该帖子已在你的收藏列表中"); } WatchlistPost watchlistPost = new WatchlistPost(); watchlistPost.setPost(post); watchlistPost.setUser(user); postWatchlistRepository.save(watchlistPost); }
对应的Repository需添加查询方法:
public interface PostWatchlistRepository extends JpaRepository<WatchlistPost, Long> { boolean existsByUserAndPost(User user, Post post); }
5. Controller层处理异常
捕获业务层异常,返回友好响应:
@PostMapping("api/post/watchlist/{postId}") public ResponseEntity<?> addToWatchList(@PathVariable Long postId, Principal principal) { try { postService.addToWatchlist(postId, principal.getName()); return new ResponseEntity<>("帖子已加入收藏列表", HttpStatus.OK); } catch (RuntimeException e) { return new ResponseEntity<>(e.getMessage(), HttpStatus.BAD_REQUEST); } }
内容的提问来源于stack exchange,提问作者ServletException
相关产品推荐
相关产品推荐

