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

Hibernate 5 進階映射全解析:聯合主鍵、一對一關聯(XML/註解)、組件映射與領域驅動設計(DDD)實戰

     編輯:Java 綜合教學

Hibernate 5 進階映射全解析:聯合主鍵、一對一關聯(XML/註解)、組件映射與領域驅動設計(DDD)實戰

Hibernate 映射的核心難點在於關聯關系建模與主鍵策略選擇。本文系統講解一對一關聯的四種變體、聯合主鍵映射、組件映射,並引入領域驅動設計(DDD)理念,展示從對象模型驅動數據庫架構的現代開發範式。

領域驅動設計(DDD)與 SchemaExport:對象優先,數據庫從生

傳統開發:先建表 → 反向生成實體 → 開發業務

DDD 開發:先設計領域模型(實體、值對象、聚合根) → 映射配置 → SchemaExport 自動生成 DDL 腳本 → 執行建表

// 一鍵生成數據庫腳本並導入
AnnotationConfiguration config = new AnnotationConfiguration().configure();
SchemaExport export = new SchemaExport(config);
// true: 輸出 SQL 到控制台, true: 直接執行建表
export.create(true, true);

優勢:模型變更只需修改實體/映射,重跑 SchemaExport 即可同步數據庫,消除手工維護 DDL 的錯誤與不同步風險。

一、一對一單向外鍵關聯

場景:學生 — 身份證,學生表持有身份證外鍵 pid,身份證表無外鍵。

註解映射

// 身份證:被引用方,無外鍵
@Entity
public class IdCard {
    @Id
    @GeneratedValue(generator = "pid")
    @GenericGenerator(name = "pid", strategy = "assigned")
    private String pid;
    private String province;
    // Getter/Setter
}

// 學生:主控方,持有外鍵
@Entity
public class Student {
    @Id @GeneratedValue
    private Integer sid;
    private String sname;
    
    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "pid", unique = true) // 外鍵列,唯一約束
    private IdCard cardId;
    // Getter/Setter
}

XML 映射

<!-- IdCard.hbm.xml -->
<class name="IdCard" table="idcard">
    <id name="pid" column="pid"><generator class="assigned"/></id>
    <property name="province"/>
</class>

<!-- Student.hbm.xml -->
<class name="Student" table="student">
    <id name="sid"><generator class="native"/></id>
    <property name="sname"/>
    <many-to-one name="cardId" column="pid" unique="true"/> <!-- 關鍵:many-to-one + unique=true -->
</class>

保存順序鐵律

session.save(idCard); // 先保存被引用方(無外鍵依賴)
session.save(student); // 再保存主控方(持有外鍵)

二、一對一雙向外鍵關聯

場景:雙方互持引用,但必須指定一方為關係擁有者(mappedBy),避免雙向維護導致死循環。

註解映射:學生為擁有者

// 學生:擁有者,維護關係
@Entity
public class Student {
    @Id @GeneratedValue
    private Integer sid;
    private String sname;
    
    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "pid", unique = true)
    private IdCard cardId; // 外鍵在學生表
    // Getter/Setter
}

// 身份證:被擁有方,mappedBy 指向擁有者的屬性名
@Entity
public class IdCard {
    @Id @GenericGenerator(...)
    private String pid;
    private String province;
    
    @OneToOne(mappedBy = "cardId") // 放棄維護權
    private Student stu;
    // Getter/Setter
}

反轉擁有者:身份證為擁有者

// 身份證:擁有者,外鍵移至身份證表
@Entity
public class IdCard {
    @Id ...
    private String pid;
    private String province;
    
    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumn(name = "sid", unique = true)
    private Student stu; // 外鍵在身份證表
}

// 學生:被擁有方
@Entity
public class Student {
    @Id @GeneratedValue
    private Integer sid;
    private String sname;
    
    @OneToOne(mappedBy = "stu")
    private IdCard cardId;
}

生成表結構差異:擁有者表包含外鍵列,被擁有者表無外鍵。保存順序隨擁有者變化:先保存無外鍵的一方。

XML 映射雙向

<!-- Student.hbm.xml (擁有者) -->
<many-to-one name="cardId" column="pid" unique="true"/>

<!-- IdCard.hbm.xml (被擁有者) -->
<one-to-one name="stu" property-ref="cardId"/> <!-- property-ref 指向擁有者屬性 -->

三、聯合主鍵

定義:多列組合為主鍵,如訂單號 + 序列號唯一標識補充訂單。

註解映射

// 聯合主鍵類:必須實現 Serializable、重寫 equals/hashCode
@Embeddable
public class IdCardPK implements Serializable {
    private String pid;
    private String bloodType; // 新增血型作為聯合主鍵一部分
    // Getter/Setter, equals, hashCode
}

// 身份證:使用 @EmbeddedId
@Entity
public class IdCard {
    @EmbeddedId
    private IdCardPK pk;
    private String province;
    
    @OneToOne(mappedBy = "cardId")
    private Student stu;
}

// 學生:@JoinColumns 映射多列外鍵
@Entity
public class Student {
    @Id @GeneratedValue
    private Integer sid;
    private String sname;
    
    @OneToOne(cascade = CascadeType.ALL)
    @JoinColumns({
        @JoinColumn(name = "pid", referencedColumnName = "pid"),
        @JoinColumn(name = "bloodType", referencedColumnName = "bloodType")
    })
    private IdCard cardId;
}

XML 映射

<!-- IdCard.hbm.xml -->
<class name="IdCard" table="idcard">
    <composite-id name="pk" class="IdCardPK">
        <key-property name="pid" column="pid"/>
        <key-property name="bloodType" column="blood_type"/>
        <generator class="assigned"/>
    </composite-id>
    <property name="province"/>
</class>

<!-- Student.hbm.xml -->
<many-to-one name="cardId">
    <column name="pid" unique="true"/>
    <column name="blood_id"/> <!-- 對應 bloodType -->
</many-to-one>

四、一對一組件映射

本質:單表,組件類無 OID(無主鍵、無獨立表),作為宿主實體的值對象嵌入。

註解映射

// 組件類:純 POJO,無任何註解
public class IdCard {
    private String pid;
    private String province;
    // Getter/Setter
}

// 宿主實體:@Embedded 嵌入組件
@Entity
public class Student {
    @Id @GeneratedValue
    private Integer sid;
    private String sname;
    
    @Embedded
    private IdCard cardId; // 字段展平到 student 表
    // Getter/Setter
}

生成表結構:student(sid, sname, pid, province),無 idcard 表。

XML 映射

<class name="Student" table="student">
    <id name="sid"><generator class="native"/></id>
    <property name="sname"/>
    <component name="cardId" class="IdCard">
        <property name="pid" column="pid"/>
        <property name="province"/>
    </component>
</class>

組件映射優勢與 DDD 值對象對應

維度實體關聯組件/值對象
標識有 OID (ID)無 OID,由宿主標識
生命周期獨立,可被多方引用依附宿主,隨宿主創建/銷毀
表結構獨立表 + 外鍵字段展平到宿主表
DDD 對應Entity (實體)Value Object (值對象)
復用性引用共享嵌入複製,不同宿主可嵌入同類型組件

DDD 實戰建模示例:

// 細粒度領域模型
public class User {
    private UserId id;           // 實體標識
    private Name name;           // 值對象:firstName + lastName
    private Contact contact;     // 值對象:email + phone + address
    private List<Address> addresses; // 實體集合(地址有獨立生命周期)
}

// 對應粗粒度數據庫表:user(id, first_name, last_name, email, phone, address)
// Address 獨立表:address(id, user_id, street, city, zip)

常見錯誤與避坑清單

  1. NoClassDefFoundError: javax/persistence/Cacheable:缺 hibernate-jpa-2.0-api-1.0.1.Final.jar
  2. NoSuchMethodError: OneToOne.orphanRemoval():ejb3-persistence.jar 與 hibernate-jpa-2.0-api.jar 衝突,刪除前者
  3. 主配置文件缺 mapping 資源:<mapping class="..."/> 或 <mapping resource="..."/> 必須齊全
  4. 註解與 XML 混用映射衝突:同一實體不可同時用兩種映射
  5. 聯合主鍵類缺 Serializable/equals/hashCode:必須實現,否則二級緩存/脫管對象比較失效
  6. Specified key was too long (1024 bytes):MySQL 5.6- / UTF8MB4 下聯合主鍵列總長度超限,解決:升級 MySQL、縮短列長度、改用單列代理主鍵

一對一映射選型決策樹

是否需要雙向導航?
  ├─ 否 → 單向外鍵(many-to-one + unique=true)✅ 最簡單
  └─ 是 → 是否共享主鍵?
        ├─ 是 → 聯合主鍵 / 主鍵共享 (@MapsId / @PrimaryKeyJoinColumn)
        └─ 否 → 雙向外鍵(指定 mappedBy 擁有者)
              ├─ 外鍵在哪張表?由業務語義決定(誰持有誰的生命周期)
              └─ 保存順序:先保存無外鍵的一方
              
是否為值對象(無獨立標識、生命周期綁定宿主)?
  ├─ 是 → 組件映射 (@Embedded / ) ✅ 單表、高性能
  └─ 否 → 實體關聯(上述各類一對一)

掌握這套映射體系,不僅能應對複雜的關聯建模挑戰,更能踐行 DDD「領域模型驅動數據庫設計」的核心思想,讓持久層真正成為領域模型的忠實投影,而非業務邏輯的妥協產物。

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