首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 數據庫知識 >  >> MySQL 資料庫 >> MySQL 綜合教學

Hibernate 多對多關聯映射:單向與雙向完整實戰

     編輯:MySQL 綜合教學

Hibernate 多對多關聯映射:單向與雙向完整實戰

多對多關聯在領域模型中極為常見(如「學生-課程」、「用戶-角色」、「教師-學生」)。Hibernate 透過中間表 (Join Table) 實現,本文以「教師-學生」為例,完整示範單向與雙向映射的註解配置、生成 DDL 與最佳實踐。

一、領域模型與資料庫對照

實體表名主鍵關鍵欄位
Teacherteacheridname
Studentstudentidname
中間表t_s (自定義)teacher_id, student_id (聯合主鍵)兩外鍵分別指向 teacher.id、student.id

二、單向關聯:教師知曉學生,學生不知教師

1. 實體類定義

@Entity
public class Teacher {
    @Id @GeneratedValue
    private Integer id;
    private String name;

    @ManyToMany
    @JoinTable(
        name = "t_s",
        joinColumns = @JoinColumn(name = "teacher_id"),
        inverseJoinColumns = @JoinColumn(name = "student_id")
    )
    private Set<Student> students = new HashSet<>();

    // getters/setters 略
}

@Entity
public class Student {
    @Id @GeneratedValue
    private Integer id;
    private String name;
    // 無反向導航屬性
    // getters/setters 略
}

2. 生成 DDL (Hibernate 自動建表)

create table teacher (
    id integer not null auto_increment,
    name varchar(255),
    primary key (id)
);

create table student (
    id integer not null auto_increment,
    name varchar(255),
    primary key (id)
);

create table t_s (
    teacher_id integer not null,
    student_id integer not null,
    primary key (teacher_id, student_id)
);

alter table t_s add constraint FK_teacher foreign key (teacher_id) references teacher (id);
alter table t_s add constraint FK_student foreign key (student_id) references student (id);

3. 使用場景

  • 僅需從「擁有方」查詢關聯集合。
  • 避免雙向導航帶來的序列化循環、級聯刪除誤傷、記憶體雙份持有。
  • 效能略優於雙向(少維護反向集合)。

三、雙向關聯:教師知學生,學生也知教師

1. 實體類定義 (關鍵:mappedBy 指定擁有方)

@Entity
public class Teacher {
    @Id @GeneratedValue
    private Integer id;
    private String name;

    @ManyToMany
    @JoinTable(
        name = "t_s",
        joinColumns = @JoinColumn(name = "teacher_id"),
        inverseJoinColumns = @JoinColumn(name = "student_id")
    )
    private Set<Student> students = new HashSet<>();
    // getters/setters
}

@Entity
public class Student {
    @Id @GeneratedValue
    private Integer id;
    private String name;

    @ManyToMany(mappedBy = "students")  // 關鍵:放棄關聯維護權,指向擁有方屬性名
    private Set<Teacher> teachers = new HashSet<>();
    // getters/setters
}

2. 生成 DDL:與單向完全相同

中間表結構無差異,mappedBy 僅影響 Hibernate 運行時行為,不影響 Schema。

3. 雙向關聯維護規範 (極重要)

// 在 Teacher 端提供便利方法,確保雙向一致性
public void addStudent(Student student) {
    this.students.add(student);
    student.getTeachers().add(this);  // 同步反向
}

public void removeStudent(Student student) {
    this.students.remove(student);
    student.getTeachers().remove(this);
}

鐵律:只在擁有方 (Teacher) 修改集合,並在便利方法中同步反向;反向方 (Student) 的 setter 僅供 Hibernate 內部使用,業務代碼嚴禁直接調用。

四、進階配置與效能優化

需求配置說明
自定義中間表欄位名@JoinColumn(name="t_id") / @JoinColumn(name="s_id")適配既有資料庫命名規範
中間表額外欄位 (如建立時間)改用 @OneToMany + 中間實體類純多對多無法映射中間表額外欄位
延遲載入 (預設)@ManyToMany(fetch = FetchType.LAZY)避免載入大量關聯對象,配合 DTO 投影查詢
急切載入@EntityGraph 或 JPQL JOIN FETCH特定業務場景一次性載入,防止 N+1
級聯操作cascade = {CascadeType.PERSIST, CascadeType.MERGE}謹慎使用 REMOVE,會刪除被關聯實體!
唯一約束uniqueConstraints = @UniqueConstraint(columnNames = {"teacher_id","student_id"})DDL 已含聯合主鍵,通常無需額外聲明

五、常見陷阱與避坑指南

  1. 雙向未同步導致關聯丟失:只在一方 setStudents() 不同步反向,持久化時 Hibernate 以擁有方為準,反向關聯不生效。
  2. 級聯刪除誤刪被關聯實體:cascade = CascadeType.ALL 或 REMOVE 會刪除 Student,通常業務不允許。建議僅 PERSIST, MERGE。
  3. 序列化循環:JSON 序列化時雙向導航造成無限遞歸。解決:@JsonIgnore (Jackson) 或 @ToString.Exclude (Lombok) 標記反向屬性。
  4. 大集合效能災難:教師關聯 1 萬學生,teacher.getStudents() 觸發全量載入。解決:@BatchSize(size=100)、分頁查詢 DTO、或拆分為單向關聯 + 專用查詢方法。
  5. 中間表缺乏索引:Hibernate 自動建表已含聯合主鍵 (雙向索引),但若頻繁「以學生查教師」,需額外單列索引 @Index(name="idx_student", columnList="student_id") (需配合 @Table(indexes=...) 在中間實體類上,純註解映射無法直接加索引,建議手動 DDL)。

六、XML 映射對照 (舊專案維護用)

<set name="students" table="t_s" lazy="true">
    <key column="teacher_id"/>
    <many-to-many class="Student" column="student_id"/>
</set>
-- 雙向在 Student 端
<set name="teachers" table="t_s" inverse="true" lazy="true">
    <key column="student_id"/>
    <many-to-many class="Teacher" column="teacher_id"/>
</set>

inverse="true" 對應註解的 mappedBy,表示放棄關聯維護權。

七、選型決策樹

是否需要從 Student 端導航到 Teachers?
  ├─ 否 → 單向 @ManyToMany (Teacher 擁有方) ✅ 推薦
  └─ 是 → 雙向 @ManyToMany + mappedBy
           ├─ 集合較小、頻雙向遍歷 → 可接受
           ├─ 集合大、高併發 → 拆分為兩個單向關聯 + 專用 Repository 查詢方法
           └─ 中間表需額外欄位 → 改用 @OneToMany + 關聯實體類
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved