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

Hibernate 關聯映射配置全解析:多對一、一對多、多對多 XML 與註解對照

     編輯:Java 綜合教學

Hibernate 關聯映射配置全解析:多對一、一對多、多對多 XML 與註解對照

關聯映射是 Hibernate ORM 核心能力,將資料庫外鍵關係映射為物件導航關係。本文系統整理單向/雙向多對一、一對多、多對多四大關聯類型的 XML (HBM) 與 JPA 註解配置,含關鍵屬性解析、級聯策略、inverse 反向控制與最佳實踐。

一、領域模型與表結構

-- 部門表
CREATE TABLE dept (
    dept_no INT PRIMARY KEY AUTO_INCREMENT,
    dname VARCHAR(50), loc VARCHAR(50)
);

-- 員工表
CREATE TABLE emp (
    emp_no INT PRIMARY KEY AUTO_INCREMENT,
    ename VARCHAR(50),
    dept_no INT,
    FOREIGN KEY (dept_no) REFERENCES dept(dept_no)
);

-- 項目表
CREATE TABLE project (
    pro_no INT PRIMARY KEY AUTO_INCREMENT,
    pname VARCHAR(50)
);

-- 員工-項目關聯表
CREATE TABLE proemp (
    rpro_no INT, remp_no INT,
    PRIMARY KEY (rpro_no, remp_no),
    FOREIGN KEY (rpro_no) REFERENCES project(pro_no),
    FOREIGN KEY (remp_no) REFERENCES emp(emp_no)
);

二、多對一單向關聯 (多端 → 一端)

場景:員工屬於一部門,Employee 導航 Dept,Dept 不導航 Employee。

XML 配置 (Employee.hbm.xml)

<class name="Employee" table="emp">
    <id name="empno" column="emp_no"><generator class="native"/></id>
    <property name="ename" column="ename"/>
    
    <!-- 多對一:name=屬性名,column=外鍵列,class=目標實體 -->
    <many-to-one name="dept" column="dept_no" class="Dept"/>
</class>

註解配置

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "dept_no")  // 外鍵列名
private Dept dept;

三、雙向一對多關聯 (一端 ↔ 多端)

場景:部門擁有多員工,員工屬於一部門,雙向導航。

XML 配置

<!-- Dept.hbm.xml (一端) -->
<class name="Dept" table="dept">
    <id name="deptNo" column="dept_no"><generator class="native"/></id>
    <property name="dname" column="dname"/>
    
    <!-- 一對多:set 集合,key=外鍵列,one-to-many=目標實體 -->
    <set name="emps" cascade="save-update" inverse="true">
        <key column="dept_no"/>
        <one-to-many class="Employee"/>
    </set>
</class>

<!-- Employee.hbm.xml (多端) -->
<class name="Employee" table="emp">
    <id name="empno" column="emp_no"><generator class="native"/></id>
    <property name="ename" column="ename"/>
    <many-to-one name="dept" column="dept_no" class="Dept"/>
</class>

註解配置

// Dept.java (一端 - 反向端)
@OneToMany(mappedBy = "dept", cascade = CascadeType.ALL)
private Set<Employee> emps = new HashSet<>();

// Employee.java (多端 - 維護端)
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "dept_no")
private Dept dept;

關鍵點

  • inverse="true" (XML) / mappedBy (註解):指定反向端,放棄關係維護權
  • 維護端:Employee (多端),負責更新外鍵列 dept_no
  • cascade:save-update (持久化/更新傳播)、delete (刪除傳播)、all (全傳播)
  • 雙邊同步:dept.getEmps().add(emp); emp.setDept(dept); 保證對象圖一致

四、單向多對多關聯

場景:員工參與多項目,僅 Employee 導航 Project,Project 不導航 Employee。

XML 配置 (Employee.hbm.xml)

<class name="Employee" table="emp">
    <id name="empno" column="emp_no"><generator class="native"/></id>
    <property name="ename" column="ename"/>
    <many-to-one name="dept" column="dept_no" class="Dept"/>
    
    <!-- 多對多:set 集合,table=關聯表,key=本端外鍵,many-to-many=對端外鍵+類 -->
    <set name="projects" table="proemp" inverse="true">
        <key column="remp_no"/>
        <many-to-many class="Project" column="rpro_no"/>
    </set>
</class>

註解配置

@ManyToMany
@JoinTable(
    name = "proemp",
    joinColumns = @JoinColumn(name = "remp_no"),      // 本端外鍵
    inverseJoinColumns = @JoinColumn(name = "rpro_no") // 對端外鍵
)
private Set<Project> projects = new HashSet<>();

五、雙向多對多關聯

場景:員工參與多項目,項目包含多員工,雙向導航。

XML 配置

<!-- Employee.hbm.xml (維護端 inverse=false 默認) -->
<set name="projects" table="proemp" cascade="save-update">
    <key column="remp_no"/>
    <many-to-many class="Project" column="rpro_no"/>
</set>

<!-- Project.hbm.xml (反向端 inverse=true) -->
<set name="employees" table="proemp" cascade="save-update" inverse="true">
    <key column="rpro_no"/>
    <many-to-many class="Employee" column="remp_no"/>
</set>

註解配置

// Employee.java (維護端)
@ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
@JoinTable(
    name = "proemp",
    joinColumns = @JoinColumn(name = "remp_no"),
    inverseJoinColumns = @JoinColumn(name = "rpro_no")
)
private Set<Project> projects = new HashSet<>();

// Project.java (反向端)
@ManyToMany(mappedBy = "projects")
private Set<Employee> employees = new HashSet<>();

六、關鍵屬性對照表

概念XML (HBM)JPA 註解說明
多對一<many-to-one name="dept" column="dept_no" class="Dept"/>@ManyToOne @JoinColumn(name="dept_no")多端持有單個一端引用
一對多<set name="emps"><key column="dept_no"/><one-to-many class="Employee"/></set>@OneToMany(mappedBy="dept")一端持有多端集合
多對多<set name="projects" table="proemp"><key column="remp_no"/><many-to-many class="Project" column="rpro_no"/></set>@ManyToMany @JoinTable(...)雙端持有對方集合,需中間表
維護端inverse="false" (默認)無 mappedBy (或 @JoinTable 定義端)負責更新外鍵/中間表
反向端inverse="true"mappedBy="屬性名"放棄維護,僅導航查詢
級聯持久化cascade="save-update"cascade={PERSIST, MERGE}保存/更新傳播到關聯對象
級聯刪除cascade="delete"cascade=REMOVE刪除傳播 (慎用!)
級聯全傳播cascade="all"cascade=ALL所有操作傳播
孤兒刪除cascade="delete-orphan" (僅 one-to-many)orphanRemoval=true從集合移除即刪除實體

七、關聯操作代碼範例

1. 部門-員工 (雙向一對多)

// 新增部門及員工 (級聯保存)
Dept dept = new Dept(); dept.setDname("技術部");
Employee emp1 = new Employee(); emp1.setEname("張三");
Employee emp2 = new Employee(); emp2.setEname("李四");

// 雙邊建立關聯
dept.getEmps().add(emp1); dept.getEmps().add(emp2);
emp1.setDept(dept); emp2.setDept(dept);

session.save(dept);  // cascade="save-update" 自動保存 emp1, emp2

2. 員工-項目 (雙向多對多)

Employee emp = session.get(Employee.class, 1L);
Project p1 = session.get(Project.class, 1L);
Project p2 = session.get(Project.class, 2L);

// 雙邊添加關聯
emp.getProjects().add(p1); emp.getProjects().add(p2);
p1.getEmployees().add(emp); p2.getEmployees().add(emp);

session.saveOrUpdate(emp);  // 維護端操作同步中間表

3. 移除關聯 (不刪除實體)

// 僅移除員工與項目關聯
emp.getProjects().remove(p1);
p1.getEmployees().remove(emp);
session.update(emp);

八、常見陷阱避坑指南

陷阱現象根因解決
雙向一對多雙方 inverse=false外鍵更新兩次、效能差雙方都維護關係一端 mappedBy/inverse=true
多對多雙方 inverse=false中間表主鍵衝突異常雙方都 INSERT 中間表一方 inverse=true
多對多雙方 inverse=true中間表無數據雙方都放棄維護一方 inverse=false
單邊設置關聯關係不持久化/對象圖不一致維護端未感知變化雙邊同步設置
cascade="delete" 刪除一端誤刪關聯對象實體刪除傳播到多端用 save-update 或手動管理
Set 未初始化NPE聲明未實例化new HashSet<>()
實體無 equals/hashCodeSet 去重失效、異常集合元素需正確實現基於 ID 實現

九、效能優化建議

  1. 延遲加載:默認 LAZY,避免誤觸發大量查詢
  2. 批量抓取:@BatchSize(size=20) 或 hibernate.default_batch_fetch_size=20 解決 N+1
  3. 維護端選擇:選集合較小端作維護端 (inverse=false)
  4. 避免 toString 觸發加載:不要在 toString() 中訪問關聯集合
  5. 大集合分頁:@LazyCollection(LazyCollectionOption.EXTRA) 或 HQL 分頁查詢

核心心法:關聯映射的靈魂是「誰維護外鍵/中間表」。XML 用 inverse,註解用 mappedBy。維護端操作集合同步數據庫,反向端僅導航。雙邊內存同步保證對象圖一致。選擇集合較小端維護是效能鐵律。級聯策略謹慎選擇,delete/all 極易誤刪數據。

掌握關聯映射配置,能讓 ORM 真正發揮「對象導航替代 SQL JOIN」的生產力優勢,同時避免 N+1、死循環、數據不一致等經典坑。

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