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

Hibernate 一對多雙向關聯映射完整實戰:注解與 XML 雙配置、級聯操作、避坑指南

     編輯:Java 相關知識

場景建模

客戶 - 訂單:一個客戶擁有多個訂單,訂單屬於一個客戶。雙向關意味著:Customer.orders 集合與 Order.customer 引用同步維護。

實體類設計 (JPA 註解版)

@Entity
@Table(name = "CUSTOMER")
public class Customer {
    @Id @GeneratedValue(generator = "uuid")
    @GenericGenerator(name = "uuid", strategy = "uuid2")
    @Column(name = "C_ID", length = 32)
    private String id;

    @Column(name = "CNAME", length = 100)
    private String name;

    @Column(name = "BANK", length = 100)
    private String bank;

    @Column(name = "PHONE", length = 20)
    private String phone;

    // 一對多雙向:mappedBy 指向 Order 端的關聯屬性名
    @OneToMany(mappedBy = "customer", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
    @OrderBy("createTime DESC")
    private Set<Order> orders = new LinkedHashSet<>();

    // Getter/Setter 省略
    // 關鍵:雙向同步輔助方法
    public void addOrder(Order order) {
        orders.add(order);
        order.setCustomer(this);
    }
    public void removeOrder(Order order) {
        orders.remove(order);
        order.setCustomer(null);
    }
}

@Entity
@Table(name = "ORDERS")
public class Order {
    @Id @GeneratedValue(generator = "uuid")
    @GenericGenerator(name = "uuid", strategy = "uuid2")
    @Column(name = "O_ID", length = 32)
    private String id;

    @Column(name = "ORDER_NO", length = 17, unique = true)
    private String orderNo;

    @Column(name = "MONEY", precision = 10, scale = 2)
    private BigDecimal money;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "CUSTOMER_ID", nullable = false)
    private Customer customer;

    // Getter/Setter 省略
}

XML 映射文件版

Customer.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.example.entity">
  <class name="Customer" table="CUSTOMER">
    <id name="id" column="C_ID" type="string" length="32">
      <generator class="uuid2"/>
    </id>
    <property name="name" column="CNAME" length="100"/>
    <property name="bank" column="BANK" length="100"/>
    <property name="phone" column="PHONE" length="20"/>
    <!-- 一對多雙向:inverse=true 由關係維護端 (Order) 負責外鍵更新 -->
    <set name="orders" cascade="all-delete-orphan" inverse="true" lazy="true">
      <key column="CUSTOMER_ID" not-null="true"/>
      <one-to-many class="Order"/>
    </set>
  </class>
</hibernate-mapping>

Order.hbm.xml

<hibernate-mapping package="com.example.entity">
  <class name="Order" table="ORDERS">
    <id name="id" column="O_ID" type="string" length="32">
      <generator class="uuid2"/>
    </id>
    <property name="orderNo" column="ORDER_NO" length="17" unique="true"/>
    <property name="money" column="MONEY" type="big_decimal"/>
    <!-- 多對一:關係維護端,外鍵列 CUSTOMER_ID -->
    <many-to-one name="customer" class="Customer" column="CUSTOMER_ID" 
                 cascade="save-update" lazy="proxy" not-null="true"/>
  </class>
</hibernate-mapping>

核心配置詳解

屬性位置作用推薦值
mappedBy@OneToMany聲明雙向關聯的擁有方屬性名"customer"
inverse="true"<set> (XML)放棄關係維護權,由多端負責外鍵 UPDATEtrue (雙向必須)
cascade兩端持久化操作傳播一端 ALL / 多端 save-update
orphanRemoval@OneToMany從集合移除實體時自動刪除 (JPA 2.0+)true
fetch兩端加載策略LAZY (生產必須)
@JoinColumn@ManyToOne指定外鍵列名、非空、唯一約束name="CUSTOMER_ID", nullable=false

測試代碼與預期 SQL

@Transactional
public void testBidirectional() {
    Customer customer = new Customer();
    customer.setName("清華大學出版社");
    customer.setBank("9559501012356789");
    customer.setPhone("010-62772015");

    Order order = new Order();
    order.setOrderNo(String.valueOf(System.currentTimeMillis()));
    order.setMoney(new BigDecimal("123.45"));

    // 雙向綁定 (關鍵!)
    customer.addOrder(order); // 內部同時設置 order.customer = customer

    session.persist(customer); // 級聯保存 order

    // 預期 SQL 順序:
    // 1. INSERT INTO CUSTOMER ...
    // 2. INSERT INTO ORDERS ... CUSTOMER_ID = ?
    // 無額外 UPDATE (因 inverse=true / mappedBy)
}

常見坑點與解決

  1. 忘記雙向綁定:只設置 customer.addOrder(order) 而不設置 order.setCustomer(customer),導致外鍵為 NULL 或額外 UPDATE。解決:在實體類提供 addOrder/removeOrder 封裝雙向綁定邏輯。
  2. 缺少 inverse="true" / mappedBy:雙端都維護關係,產生兩條 UPDATE 外鍵語句,性能損耗且可能死鎖。解決:一端 (集合端) 設置 inverse="true" 或 mappedBy。
  3. 級聯刪除誤傷:cascade="all-delete-orphan" 刪除 Customer 會級聯刪除所有 Order。若 Order 被其他實體引用,需謹慎使用或改為手動管理。
  4. 懶加載異常 (LazyInitializationException):Session 關閉後訪問 customer.getOrders()。解決:Open Session In View / DTO 投影 / fetch join / @BatchSize。
  5. Set 集合需正確實現 equals/hashCode:基於業務主鍵 (id) 實現,避免重複元素判斷失效。

最佳實踐總結

  • 優先使用註解:類與映射同文件,重構友好,IDE 支援好。
  • 雙向關聯只在必要時使用:單向 @OneToMany + @JoinColumn 往往足夠,避免雙向維護複雜度。
  • 集合類型選 Set:語義上無序不重複,Hibernate 更新效率高於 List (List 需維護 index 列)。
  • orphanRemoval = true:替代 cascade="delete-orphan",語義更清晰 (JPA 標準)。
  • 測試 SQL 輸出:開啟 hibernate.show_sql=true hibernate.format_sql=true,檢查是否有多餘 UPDATE/SELECT。

總結

Hibernate 一對多雙向關聯核心在於「一端維護關係、另一端放棄維護 (inverse/mappedBy)、級聯操作精準控制、雙向綁定封裝在實體方法中」。掌握註解與 XML 兩種配置方式,理解背後的外鍵 UPDATE 機制,即可避開 99% 的關聯映射坑點,構建高性能、可維護的持久層模型。

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