JPA多对多关系添加created_at列异常:List无效Map生成主键
JPA多对多关联添加非主键created_at列的解决方案
问题分析
- 使用
List<Users>结合@Column注解无法生效:@Column用于标注实体字段与数据库列的映射,而@ManyToMany的中间表由JPA自动维护,直接在关联字段上添加@Column会被JPA忽略,无法在中间表生成created_at列。 - 使用
Map<Timestamp, Users>结合@MapKeyJoinColumn注解导致列成为主键:@MapKeyJoinColumn用于指定Map的key对应的关联列,JPA会将该列作为中间表复合主键的一部分,不符合非主键的需求。
正确解决方案:拆分多对多为两个一对多
当中间表需要额外字段(如created_at)时,JPA的@ManyToMany无法满足需求,必须显式创建中间关联实体,通过两个@OneToMany关联原实体。
1. 创建中间关联实体Follow
@Entity @Table(name = "follows") public class Follow { @EmbeddedId private FollowId id; @ManyToOne(fetch = FetchType.LAZY) @MapsId("userId") @JoinColumn(name = "user_id") private User user; @ManyToOne(fetch = FetchType.LAZY) @MapsId("followingUserId") @JoinColumn(name = "following_user_id") private User followingUser; @Column(name = "created_at", nullable = false) private Timestamp createdAt; @PrePersist protected void onCreate() { createdAt = new Timestamp(System.currentTimeMillis()); } // 构造方法、Getter、Setter }
2. 创建复合主键类FollowId
@Embeddable public class FollowId implements Serializable { @Column(name = "user_id") private Long userId; @Column(name = "following_user_id") private Long followingUserId; // 必须实现equals和hashCode方法 @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; FollowId followId = (FollowId) o; return Objects.equals(userId, followId.userId) && Objects.equals(followingUserId, followId.followingUserId); } @Override public int hashCode() { return Objects.hash(userId, followingUserId); } // 构造方法、Getter、Setter }
3. 修改原User实体
@Entity @Table(name = "users") public class User { // 原有字段(如id、username等)... @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) private List<Follow> follows = new ArrayList<>(); @OneToMany(mappedBy = "followingUser", cascade = CascadeType.ALL, orphanRemoval = true) private List<Follow> followers = new ArrayList<>(); // 便捷方法:添加关注操作 public void follow(User targetUser) { Follow follow = new Follow(); follow.setUser(this); follow.setFollowingUser(targetUser); this.follows.add(follow); targetUser.getFollowers().add(follow); } // Getter、Setter }
说明
这种方式通过显式管理中间表实体,既能保留created_at字段,又不会将其设为主键,完全符合需求。同时,通过@PrePersist注解可以自动填充创建时间,无需手动设置。
内容的提问来源于stack exchange,提问作者Midou Hsk
相关产品推荐
相关产品推荐

