一個員工可參與多個項目,一個項目可包含多個員工。數據庫需中間表 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 Setprojects = 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="...">:顯式指定中間表名、外鍵列名,建議始終顯式配置,避免默認命名策略變更導致不兼容。role 字段,也要升級中間實體,避免後期重構痛楚。inverse="true":否則雙方各執行一次 INSERT,主鍵衝突或數據重複。Set 而非 List:避免重複元素,Hibernate 對 Set 去重性能優於 List (需索引列)。LazyInitializationException,Service 層用 Hibernate.initialize() 或 DTO 投影解決。Hibernate 多對多映射的核心抉擇在於 中間表是否承載業務語義。純關係用原生 <many-to-many>,帶屬性用中間實體 <many-to-one> 雙向組合。配置關鍵在 inverse、cascade、orphanRemoval 三大屬性的精準組合,配合 Set 集合與顯式 JoinTable,構建健壯、可維護的關係映射。