一對多雙向關聯是 Hibernate 中最常見的關聯模型(如:客戶-訂單、部門-員工)。本文以「客戶-訂單」為例,完整展示實體類別設計、.hbm.xml 映射配置、雙向關聯維護、級聯操作策略,以及測試程式碼,幫助您避開「多方維護外鍵、單方 inverse=true、級聯刪除誤傷」等經典坑點。
c_id (PK)、c_name、bank、phonec_id (PK)、order_no、money、customer_id (FK → Customer.c_id)
// Customer.java
package com.example.model;
import java.util.HashSet;
import java.util.Set;
public class Customer {
private String id;
private String cName;
private String bank;
private String phone;
private Set orders = new HashSet<>(); // 集合型別用 Set 避免重複
// Getter/Setter 略...
public Set getOrders() { return orders; }
public void setOrders(Set orders) { this.orders = orders; }
// 便利方法:雙向關聯同步
public void addOrder(Order order) {
orders.add(order);
order.setCustomer(this);
}
public void removeOrder(Order order) {
orders.remove(order);
order.setCustomer(null);
}
}
// Order.java
package com.example.model;
public class Order {
private String id;
private String orderNo;
private Double money;
private Customer customer; // 多方持有單方引用
// Getter/Setter 略...
}
<?xml version="1.0" encoding="UTF-8"?>
<hibernate-mapping>
<class name="com.example.model.Customer" table="CUSTOMER">
<id name="id" column="C_ID" length="32">
<generator class="uuid.hex"/>
</id>
<property name="cName" column="CNAME" length="100"/>
<property name="bank" column="BANK" length="100"/>
<property name="phone" column="PHONE" length="20"/>
<!-- 一對多雙向:單方配置 inverse=true,放棄外鍵維護權 -->
<set name="orders" cascade="all" inverse="true" lazy="false">
<key column="CUSTOMER_ID"/>
<one-to-many class="com.example.model.Order"/>
</set>
</class>
</hibernate-mapping>
<?xml version="1.0" encoding="UTF-8"?>
<hibernate-mapping>
<class name="com.example.model.Order" table="ORDERS">
<id name="id" column="C_ID" length="32">
<generator class="uuid.hex"/>
</id>
<property name="orderNo" column="ORDERNO" length="17"/>
<property name="money" column="MONEY" type="double"/>
<!-- 多方維護外鍵,不設 inverse -->
<many-to-one name="customer" class="com.example.model.Customer"
column="CUSTOMER_ID" cascade="all" lazy="false" not-null="true"/>
</class>
</hibernate-mapping>
| 屬性 | 放置位置 | 含義 | 建議值 |
|---|---|---|---|
inverse | 單方 <set> | 關聯維護權反轉:true 表示「由多方負責更新外鍵」 | true(單方必須設) |
cascade | 雙方均可 | 級聯操作傳播:all / save-update / delete / none | 單方 all,多方視業務而定 |
not-null | 多方 <many-to-one> | 外鍵欄位不可為空,確保訂單必須屬於某客戶 | true |
lazy | 集合/多對一 | 延遲載入開關 | 測試期 false,生產 true(預設) |
@Test
public void testSaveCustomerWithOrders() {
Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();
Customer customer = new Customer();
customer.setCName("清華大學出版社");
customer.setBank("9559501012356789");
customer.setPhone("010-62772015");
Order order = new Order();
order.setOrderNo(String.valueOf(System.currentTimeMillis()));
order.setMoney(1234.56);
// 關鍵:雙向關聯雙邊都要 set
customer.addOrder(order); // 內部同時執行 order.setCustomer(customer)
session.save(customer); // 因 cascade="all",order 會自動持久化
tx.commit();
session.close();
System.out.println("客戶 ID:" + customer.getId());
}
customer.getOrders().add(order) 卻忘記 order.setCustomer(customer) → 外鍵為 NULL,拋 PropertyValueException(因 not-null="true")。addOrder() 便利方法,一次搞定雙邊。<set> 必加 inverse="true"。cascade="delete" 或 all 刪除客戶時會連帶刪除所有訂單。cascade="save-update",手動處理刪除邏輯。Set 需正確實作 equals/hashCode:若未覆寫,預設用記憶體位址比較,導致集合操作異常。equals/hashCode。// Customer
@OneToMany(mappedBy = "customer", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Set orders = new HashSet<>();
// Order
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "CUSTOMER_ID", nullable = false)
private Customer customer;
mappedBy 等同於 XML 的 inverse="true",指明由多方 Order.customer 維護關聯。
Hibernate 一對多雙向關聯的核心口訣:「多方維護外鍵、單方 inverse=true、雙邊同步 set、級聯按需配」。掌握這四點,配合便利方法封裝雙向綁定邏輯,即可在複雜業務中穩健操作關聯物件圖。