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

为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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.18 16:35:23