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

Hibernate 多對多雙向關聯兩種映射策略:中間表實體類 vs 直接集合映射

     編輯:Java 綜合教學

場景:員工與項目多對多

一個員工可參與多個項目,一個項目可包含多個員工。數據庫需中間表 pro_emp 維護關係。

策略一:中間表升級為實體類 (兩個一對多)

適用場景: 中間表攜帶業務屬性(如角色、加入時間、薪資比例),或需獨立查詢關係記錄。

實體類設計

// Employee.java
@Entity @Table(name="employee")
public class Employee {
    @Id @GeneratedValue private Integer id;
    private String name;
    @OneToMany(mappedBy="employee", cascade=CascadeType.ALL, orphanRemoval=true)
    private Set proEmps = new HashSet<>();   // 關聯中間實體
}

// Project.java
@Entity @Table(name="project")
public class Project {
    @Id @GeneratedValue private Integer id;
    private String name;
    @OneToMany(mappedBy="project", cascade=CascadeType.ALL)
    private Set proEmps = new HashSet<>();
}

// ProEmp.java (中間表實體)
@Entity @Table(name="pro_emp")
public class ProEmp {
    @Id @GeneratedValue private Integer id;
    @ManyToOne @JoinColumn(name="emp_id") private Employee employee;
    @ManyToOne @JoinColumn(name="pro_id") private Project project;
    private String role;              // 業務屬性
    private Date joinDate;            // 業務屬性
}

映射文件

<!-- ProEmp.hbm.xml 關鍵配置 -->
<class name="ProEmp" table="pro_emp">
  <id name="id"><generator class="native"/></id>
  <many-to-one name="employee" column="emp_id" class="Employee"/>
  <many-to-one name="project" column="pro_id" class="Project"/>
</class>

保存邏輯

Employee emp = new Employee(); emp.setName("李四");
Project pro = new Project(); pro.setName("一號項目");
ProEmp rel = new ProEmp(); rel.setEmployee(emp); rel.setProject(pro); rel.setRole("開發");
session.save(emp); session.save(pro); session.save(rel);  // 三方均需持久化

策略二:直接集合映射 (原生多對多)

適用場景: 純關係表,無額外屬性,追求代碼簡潔。

實體類設計

// Employee.java
@OneToMany(mappedBy="employees") // 非關係維護端
private Set projects = new HashSet<>();

// Project.java (關係維護端)
@ManyToMany
@JoinTable(
    name="pro_emp",
    joinColumns=@JoinColumn(name="pro_id"),
    inverseJoinColumns=@JoinColumn(name="emp_id")
)
private Set employees = new HashSet<>();

映射文件

<!-- Employee.hbm.xml -->
<set name="projects" table="pro_emp" inverse="true">
  <key column="emp_id"/>
  <many-to-many class="Project" column="pro_id"/>
</set>

<!-- Project.hbm.xml -->
<set name="employees" table="pro_emp" cascade="save-update">
  <key column="pro_id"/>
  <many-to-many class="Employee" column="emp_id"/>
</set>

保存邏輯

Project pro = new Project(); pro.setName("一號項目");
Employee emp = new Employee(); emp.setName("李四");
pro.getEmployees().add(emp);   // 只需維護關係維護端集合
session.save(pro);             // 級聯保存 emp 及關係

兩大策略深度對比

維度策略一:中間實體類策略二:直接集合
中間表業務屬性✅ 自然支持❌ 無法映射
關係獨立查詢✅ from ProEmp❌ 需原生 SQL
代碼簡潔度⚠️ 需額外實體✅ 極簡
級聯操作控制✅ 精確到關係級⚠️ 僅支持 save-update/delete-orphan
性能 (關係增刪)稍慢 (多一層實體)快 (直接操作集合)
遷移靈活性高 (隨時擴展屬性)低 (改結構需重寫映射)

核心配置細節解析

  • inverse="true":聲明「非關係維護端」,由對方負責維護中間表數據,避免重複 INSERT/DELETE。
  • cascade="save-update":保存/更新主實體時級聯關聯對象,不包含 delete (需 delete-orphan 或 all)。
  • orphanRemoval=true (JPA) / cascade="all-delete-orphan" (XML):從集合移除元素時自動刪除中間表記錄及關聯實體 (謹慎用)。
  • @JoinTable / <set table="...">:顯式指定中間表名、外鍵列名,建議始終顯式配置,避免默認命名策略變更導致不兼容。

最佳實踐建議

  1. 默認選策略二:純關係場景,代碼最少、性能最好。
  2. 業務屬性出現即遷移策略一:哪怕只有一個 role 字段,也要升級中間實體,避免後期重構痛楚。
  3. 雙向關聯必設 inverse="true":否則雙方各執行一次 INSERT,主鍵衝突或數據重複。
  4. 集合用 Set 而非 List:避免重複元素,Hibernate 對 Set 去重性能優於 List (需索引列)。
  5. 懶加載陷阱:多對多默認 LAZY,Session 關閉後訪問集合拋 LazyInitializationException,Service 層用 Hibernate.initialize() 或 DTO 投影解決。

總結

Hibernate 多對多映射的核心抉擇在於 中間表是否承載業務語義。純關係用原生 <many-to-many>,帶屬性用中間實體 <many-to-one> 雙向組合。配置關鍵在 inverse、cascade、orphanRemoval 三大屬性的精準組合,配合 Set 集合與顯式 JoinTable,構建健壯、可維護的關係映射。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved