Hibernate 關聯映射是 ORM 核心能力之一。本文以「部門-員工」經典單向一對多場景為例,完整演示從實體類設計、映射文件配置、建表策略到數據操作的最佳實踐,重點分析外鍵維護方向對 SQL 執行效率的影響。
// Employee.java - 多端(多個員工屬於一個部門)
package com.sw.entity;
public class Employee {
private Integer id;
private String name;
private Double salary;
private Dept dept; // 關聯部門對象(單向:員工知曉部門,部門不知曉員工集合)
// Getter/Setter 省略
public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public Double getSalary() { return salary; }
public void setSalary(Double salary) { this.salary = salary; }
public Dept getDept() { return dept; }
public void setDept(Dept dept) { this.dept = dept; }
}
// Dept.java - 一端(一個部門包含多個員工)
package com.sw.entity;
public class Dept {
private Integer id;
private String name;
// Getter/Setter 省略
public Integer getId() { return id; }
public void setId(Integer id) { this.id = id; }
public String getName() { return name; }
public void setName(String name) { this.name = name; }
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"https://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.sw.entity">
<class name="Employee" table="employee">
<id name="id" column="eid">
<generator class="native"/>
</id>
<property name="name" column="ename"/>
<property name="salary" column="salary"/>
<!-- 多端維護關聯:many-to-one 指向一端 -->
<many-to-one name="dept" class="Dept" column="did"
cascade="save-update"
not-null="true"/>
</class>
</hibernate-mapping>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE hibernate-mapping PUBLIC
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"https://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping package="com.sw.entity">
<class name="Dept" table="dept">
<id name="id" column="did">
<generator class="native"/>
</id>
<property name="name" column="dname"/>
<!-- 單向關聯:一端無 set/collection 映射 -->
</class>
</hibernate-mapping>
<!-- hibernate.cfg.xml --> <mapping resource="com/sw/entity/Employee.hbm.xml"/> <mapping resource="com/sw/entity/Dept.hbm.xml"/>
// 測試類:導出建表 SQL 或直接建表 Configuration cfg = new Configuration().configure(); SchemaExport export = new SchemaExport(cfg); export.create(true, true); // 輸出 SQL 到控制台並執行建表
生成的表結構(MySQL 為例):
CREATE TABLE dept (
did INT PRIMARY KEY AUTO_INCREMENT,
dname VARCHAR(255)
);
CREATE TABLE employee (
eid INT PRIMARY KEY AUTO_INCREMENT,
ename VARCHAR(255),
salary DOUBLE,
did INT NOT NULL,
CONSTRAINT fk_emp_dept FOREIGN KEY (did) REFERENCES dept(did)
);
// 錯誤示範
Employee e1 = new Employee(); e1.setName("張三"); e1.setSalary(5000.0);
Employee e2 = new Employee(); e2.setName("李四"); e2.setSalary(6000.0);
Dept d = new Dept(); d.setName("技術部");
session.save(e1); // INSERT employee (ename, salary, did) VALUES (?, ?, NULL)
session.save(e2); // INSERT employee ... did=NULL
session.save(d); // INSERT dept (dname) VALUES (?)
e1.setDept(d); // UPDATE employee SET did=? WHERE eid=?
e2.setDept(d); // UPDATE employee SET did=? WHERE eid=?
session.flush();
SQL 執行序列:2×INSERT (did=NULL) → 1×INSERT (dept) → 2×UPDATE (回填 did)
缺點:多次數據庫往返,外鍵列臨時為 NULL 違反 not-null 約束風險,性能差。
// 推薦做法
Dept d = new Dept(); d.setName("技術部");
session.save(d); // 先持久化一端,生成 ID
Employee e1 = new Employee(); e1.setName("張三"); e1.setSalary(5000.0); e1.setDept(d);
Employee e2 = new Employee(); e2.setName("李四"); e2.setSalary(6000.0); e2.setDept(d);
session.save(e1); // INSERT employee (ename, salary, did) VALUES (?, ?, ?) - did 已有值
session.save(e2); // INSERT employee ... did 已有值
session.flush();
SQL 執行序列:1×INSERT (dept) → 2×INSERT (employee, did 直接寫入)
優勢:無 UPDATE 開銷,單次 INSERT 完成,符合外鍵約束,性能最優。
誰維護關聯,誰最後保存;被維護方(一端)必須先持久化生成 ID。
單向一對多中,many-to-one端(Employee)維護外鍵,故 Dept 先 save,Employee 後 save 並設置 dept 引用。
映射文件中 cascade="save-update" 允許通過一端級聯保存多端(需雙向關聯配合 inverse="true"),單向關聯下級聯效果有限。雙向一對多優化將在下篇詳細講解。
| 現象 | 原因 | 解決 |
|---|---|---|
| 外鍵列為 NULL | 先 save 多端,未設置關聯再 flush | 調整保存順序,或設置 not-null="false"(不推薦) |
異常:TransientObjectException |
關聯的對象未持久化(無 ID) | 先 save 一端,或開啟 cascade="save-update" |
| 重複 INSERT | 雙向關聯雙端均維護,未設置 inverse="true" |
集合端設置 inverse="true",交給多端維護 |
<many-to-one> 映射外鍵列,一端無集合映射many-to-one 端,該端負責設置關聯對象inverse、cascade 級聯刪除/保存的完整配置