客戶 - 訂單:一個客戶擁有多個訂單,訂單屬於一個客戶。雙向關意味著:Customer.orders 集合與 Order.customer 引用同步維護。
@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 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>
<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) | 放棄關係維護權,由多端負責外鍵 UPDATE | true (雙向必須) |
| cascade | 兩端 | 持久化操作傳播 | 一端 ALL / 多端 save-update |
| orphanRemoval | @OneToMany | 從集合移除實體時自動刪除 (JPA 2.0+) | true |
| fetch | 兩端 | 加載策略 | LAZY (生產必須) |
| @JoinColumn | @ManyToOne | 指定外鍵列名、非空、唯一約束 | name="CUSTOMER_ID", nullable=false |
@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)
}
customer.addOrder(order) 而不設置 order.setCustomer(customer),導致外鍵為 NULL 或額外 UPDATE。解決:在實體類提供 addOrder/removeOrder 封裝雙向綁定邏輯。inverse="true" 或 mappedBy。cascade="all-delete-orphan" 刪除 Customer 會級聯刪除所有 Order。若 Order 被其他實體引用,需謹慎使用或改為手動管理。customer.getOrders()。解決:Open Session In View / DTO 投影 / fetch join / @BatchSize。@OneToMany + @JoinColumn 往往足夠,避免雙向維護複雜度。cascade="delete-orphan",語義更清晰 (JPA 標準)。hibernate.show_sql=true hibernate.format_sql=true,檢查是否有多餘 UPDATE/SELECT。Hibernate 一對多雙向關聯核心在於「一端維護關係、另一端放棄維護 (inverse/mappedBy)、級聯操作精準控制、雙向綁定封裝在實體方法中」。掌握註解與 XML 兩種配置方式,理解背後的外鍵 UPDATE 機制,即可避開 99% 的關聯映射坑點,構建高性能、可維護的持久層模型。