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

Hibernate中User与Reservation的多对多关联及三元关系实现问询

嘿,你的思路完全正确——当多对多关系需要在中间表添加额外属性时,用关联实体类(也就是你这里的InternalParticipant)是Hibernate的标准解决方案。我来帮你补全代码并拆解关键细节,让整个关联逻辑更清晰:

完整的关联实体实现方案

1. 复合主键类(InternalParticipantPK)

因为中间表user_has_reservation的主键是uhr_idUser和uhr_idReservation的组合,所以需要定义一个可嵌入的复合主键类,必须实现Serializable并重写equals和hashCode:

@Embeddable
public class InternalParticipantPK implements Serializable {
    private static final long serialVersionUID = 1L;
    
    @Column(name = "uhr_idUser")
    private Long userId;
    
    @Column(name = "uhr_idReservation")
    private Long reservationId;

    // 空构造函数(Hibernate要求)
    public InternalParticipantPK() {}
    
    // 带参数构造函数,方便创建实例
    public InternalParticipantPK(Long userId, Long reservationId) {
        this.userId = userId;
        this.reservationId = reservationId;
    }

    // 必须重写equals和hashCode,Hibernate用它来识别唯一实体
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        InternalParticipantPK that = (InternalParticipantPK) o;
        return Objects.equals(userId, that.userId) && Objects.equals(reservationId, that.reservationId);
    }

    @Override
    public int hashCode() {
        return Objects.hash(userId, reservationId);
    }

    // Getters 和 Setters
    public Long getUserId() { return userId; }
    public void setUserId(Long userId) { this.userId = userId; }
    public Long getReservationId() { return reservationId; }
    public void setReservationId(Long reservationId) { this.reservationId = reservationId; }
}

2. 关联实体类(InternalParticipant)

这是你已经开始写的类,我帮你补全完整逻辑,包括关联属性和中间表的额外字段:

@Entity
@Table(name = "user_has_reservation")
@AssociationOverrides({
    @AssociationOverride(name = "pk.user", joinColumns = @JoinColumn(name = "uhr_idUser")),
    @AssociationOverride(name = "pk.reservation", joinColumns = @JoinColumn(name = "uhr_idReservation"))
})
public class InternalParticipant implements java.io.Serializable {
    private static final long serialVersionUID = 1L;
    
    private InternalParticipantPK pk = new InternalParticipantPK();
    
    // 中间表的自定义属性示例:参与时间、角色
    @Column(name = "participation_date")
    private LocalDateTime participationDate;
    
    @Column(name = "role")
    private String role;

    // 关联User的快捷方法,用@Transient避免重复映射
    @Transient
    public User getUser() {
        return getPk().getUser();
    }

    public void setUser(User user) {
        getPk().setUser(user);
    }

    // 关联Reservation的快捷方法,同样用@Transient
    @Transient
    public Reservation getReservation() {
        return getPk().getReservation();
    }

    public void setReservation(Reservation reservation) {
        getPk().setReservation(reservation);
    }

    // 主键的Getter和Setter,用@EmbeddedId标注嵌入主键
    @EmbeddedId
    public InternalParticipantPK getPk() {
        return pk;
    }

    public void setPk(InternalParticipantPK pk) {
        this.pk = pk;
    }

    // 自定义属性的Getters和Setters
    public LocalDateTime getParticipationDate() { return participationDate; }
    public void setParticipationDate(LocalDateTime participationDate) { this.participationDate = participationDate; }
    public String getRole() { return role; }
    public void setRole(String role) { this.role = role; }

    // 基于主键的equals和hashCode
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        InternalParticipant that = (InternalParticipant) o;
        return Objects.equals(pk, that.pk);
    }

    @Override
    public int hashCode() {
        return Objects.hash(pk);
    }
}

3. User和Reservation实体的关联配置

需要在两个主实体中添加对InternalParticipant的一对多关联:

User实体

@Entity
@Table(name = "user")
public class User implements Serializable {
    // 其他属性(比如id、username等)
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "idUser")
    private Long id;
    
    private String username;

    // 关联InternalParticipant,mappedBy指定对方的关联属性名
    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
    private Set<InternalParticipant> participantRecords = new HashSet<>();

    // 便捷方法:添加预约关联,同时设置中间表属性
    public void addReservation(Reservation reservation, LocalDateTime participationDate, String role) {
        InternalParticipant record = new InternalParticipant();
        record.setUser(this);
        record.setReservation(reservation);
        record.setParticipationDate(participationDate);
        record.setRole(role);
        participantRecords.add(record);
        reservation.getParticipantRecords().add(record);
    }

    // Getters 和 Setters
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    public String getUsername() { return username; }
    public void setUsername(String username) { this.username = username; }
    public Set<InternalParticipant> getParticipantRecords() { return participantRecords; }
    public void setParticipantRecords(Set<InternalParticipant> participantRecords) { this.participantRecords = participantRecords; }
}

Reservation实体

@Entity
@Table(name = "reservation")
public class Reservation implements Serializable {
    // 其他属性(比如id、预约时间等)
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "idReservation")
    private Long id;
    
    private LocalDateTime reservationTime;

    // 关联InternalParticipant
    @OneToMany(mappedBy = "reservation", cascade = CascadeType.ALL, orphanRemoval = true)
    private Set<InternalParticipant> participantRecords = new HashSet<>();

    // Getters 和 Setters
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }
    public LocalDateTime getReservationTime() { return reservationTime; }
    public void setReservationTime(LocalDateTime reservationTime) { this.reservationTime = reservationTime; }
    public Set<InternalParticipant> getParticipantRecords() { return participantRecords; }
    public void setParticipantRecords(Set<InternalParticipant> participantRecords) { this.participantRecords = participantRecords; }
}

关键注解解释

  • @Embeddable:标记复合主键类,说明这个类的实例可以嵌入到实体类中作为主键。
  • @AssociationOverrides:覆盖复合主键中关联属性的映射规则,指定中间表对应的外键列名,避免默认列名冲突。
  • @Transient:标记关联属性的快捷方法,告诉Hibernate不要将这些方法映射到数据库字段,因为关联已经在复合主键中处理了。
  • @EmbeddedId:标记实体类中的复合主键属性,告诉Hibernate这是嵌入的主键。

这样配置后,你就可以通过InternalParticipant来管理User和Reservation之间的关联,同时自由操作中间表的额外属性啦!

内容的提问来源于stack exchange,提问作者luca

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.22 08:34:36