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

Hibernate 映射關係實戰(一):單向一對多關聯配置與數據操作優化

     編輯:Java 綜合教學

Hibernate 映射關係實戰(一):單向一對多關聯配置與數據操作優化

Hibernate 關聯映射是 ORM 核心能力之一。本文以「部門-員工」經典單向一對多場景為例,完整演示從實體類設計、映射文件配置、建表策略到數據操作的最佳實踐,重點分析外鍵維護方向對 SQL 執行效率的影響。

一、領域模型設計

1. 實體類定義

// 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; }
}

二、Hibernate 映射文件配置

1. Employee.hbm.xml(多端維護關聯)

<?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>

2. Dept.hbm.xml(一端無集合屬性)

<?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>

3. 核心配置註冊

<!-- hibernate.cfg.xml -->
<mapping resource="com/sw/entity/Employee.hbm.xml"/>
<mapping resource="com/sw/entity/Dept.hbm.xml"/>

三、Schema 自動生成與表結構

// 測試類:導出建表 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)
);

四、數據操作:正確順序與 SQL 分析

❌ 低效操作順序(先存多端,再存一端,再關聯)

// 錯誤示範
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",交給多端維護

七、總結

  1. 單向一對多:多端用 <many-to-one> 映射外鍵列,一端無集合映射
  2. 外鍵維護權在 many-to-one 端,該端負責設置關聯對象
  3. 操作鐵律:先持久化一端(生成 ID)→ 再持久化多端(攜帶外鍵值)
  4. 避免「先插 NULL 後 UPDATE」反模式,減少數據庫往返,提升性能
  5. 下篇將講解雙向一對多、inverse、cascade 級聯刪除/保存的完整配置
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved