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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 05:22:41