一、映射多對一關聯關系。
1.單向的多對一
(1)以 Customer 和 Order 為例:一個用戶可以發出多個訂單,而一個訂單只能屬於一個客戶。從 Order 到 Customer 是多對一關聯關系。
(2)創建 Customer 和 Order 表。

CREATE TABLE customer ( customer_id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY , customer_name VARCHAR(50) ) CREATE TABLE `order` ( order_id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, order_name VARCHAR(50), customer_id INT(11) )
(3)用 Intellij Idea 自動生成關聯關系,以及對應的 Entitiy.hbm.xml 和 持久化類。
說明:
其中 Type 是用來修飾對應的 Attribute Name 的。
在 Order 端,定義 Customer 類,一個訂單屬於一個客戶。而在 Customer 端,一個客戶可以有多個訂單,因為是單向的,所以這里放棄屬性的添加。
在 Join Columns 定義了 Order 和 Customer 之間的關聯關系,order 表中的 customer_id 外鍵和 customer 表中的 customer_id 主鍵關聯。
來看生成的 Schema:
沒有勾選 customer_id,是因為 Intellij Idea 沒法直接映射為 Customer 類型的 customer。

<hibernate-mapping> <class name="com.nucsoft.hibernate.Order" table="order" schema="hibernate"> <id name="orderId"> <column name="order_id" sql-type="int(11)"/> <generator class="native"/> </id> <property name="orderName"> <column name="order_name" sql-type="varchar(50)" length="50" not-null="true"/> </property> <many-to-one name="customer" class="com.nucsoft.hibernate.Customer"> <column name="customer_id" not-null="true"/> </many-to-one> </class> </hibernate-mapping>
使用 <many-to-one> 節點來維護多對一關聯關系。
name 屬性:多這一端關聯的一那一端的屬性的名稱。
class 屬性:關聯的一端的屬性的類型。
column 屬性:一那一端在多的一端對應的數據表中的外鍵。可以任意命名,但需要和數據表中的字段對應。
(4)單向多對一的 CRUD 以及需要注意的問題。
<1> 新增
①先保存一的一端 Customer,后保存多的一端 Order。

@Test public void testMany2OneSave() { Customer customer = new Customer(); customer.setCustomerName("aa"); Order order = new Order(); order.setOrderName("order1"); order.setCustomer(customer); Order order2 = new Order(); order2.setOrderName("order2"); order2.setCustomer(customer); session.save(customer); session.save(order); session.save(order2); }
打印 SQL:

Hibernate: insert into hibernate.customer (customer_name) values (?) Hibernate: insert into hibernate.order (order_name, customer_id) values (?, ?) Hibernate: insert into hibernate.order (order_name, customer_id) values (?, ?)
結論:發送了3條 INSERT 語句。
②先保存多的一端 Order,再保存一的一端 Customer。

@Test public void testMany2OneSave() { Customer customer = new Customer(); customer.setCustomerName("bb"); Order order = new Order(); order.setOrderName("order3"); order.setCustomer(customer); Order order2 = new Order(); order2.setOrderName("order4"); order2.setCustomer(customer); session.save(order); session.save(order2); session.save(customer); }
打印 SQL:

Hibernate: insert into hibernate.order (order_name, customer_id) values (?, ?) Hibernate: insert into hibernate.order (order_name, customer_id) values (?, ?) Hibernate: insert into hibernate.customer (customer_name) values (?) Hibernate: update hibernate.order set order_name=?, customer_id=? where order_id=? Hibernate: update hibernate.order set order_name=?, customer_id=? where order_id=?
結論:發送了3條 INSERT 語句,2條 UPDATE 語句。
總結:在單向多對一的關聯關系下,先插入 1 的一端會減少 SQL 語句的執行,性能更高。
<2>刪除
先刪除1的一端。

@Test public void testMany2OneDelete() { Customer customer = (Customer) session.get(Customer.class, 1); session.delete(customer); }
控制台打印:
Cannot delete or update a parent row: a foreign key constraint fails (`hibernate`.`order`, CONSTRAINT `FK_m6q2ofkj1g5aobtb2p00ajpqg` FOREIGN KEY (`customer_id`) REFERENCES `customer` (`customer_id`))
結論:在不設置級聯關系的前提下,不能刪除 1 的一端。
<3>更新

@Test public void testMany2OneUpdate() { Order order = (Order) session.get(Order.class, 1); order.getCustomer().setCustomerName("aaa"); }

Hibernate: select order0_.order_id as order_id1_1_0_, order0_.order_name as order_na2_1_0_, order0_.customer_id as customer3_1_0_ from hibernate.order order0_ where order0_.order_id=? Hibernate: select customer0_.customer_id as customer1_0_0_, customer0_.customer_name as customer2_0_0_ from hibernate.customer customer0_ where customer0_.customer_id=? Hibernate: update hibernate.customer set customer_name=? where customer_id=?
<4>查詢
①查詢 n 的一端,但是不使用查詢出來關聯的 1 的一端的對象。
@Test public void testMany2OneGet() { Order order = (Order) session.get(Order.class, 1); System.out.println(order.getCustomer().getClass().getName()); }
Hibernate: select order0_.order_id as order_id1_1_0_, order0_.order_name as order_na2_1_0_, order0_.customer_id as customer3_1_0_ from hibernate.order order0_ where order0_.order_id=? order1 com.nucsoft.hibernate.Customer_$$_jvst30c_1
②查詢 n 的一端,使用查詢出來關聯的 1 的一端的對象。
@Test public void testMany2OneGet() { Order order = (Order) session.get(Order.class, 1); System.out.println(order.getCustomer().getClass().getName()); order.getCustomer().getCustomerName(); }
Hibernate: select order0_.order_id as order_id1_1_0_, order0_.order_name as order_na2_1_0_, order0_.customer_id as customer3_1_0_ from hibernate.order order0_ where order0_.order_id=? com.nucsoft.hibernate.Customer_$$_jvst30c_1 Hibernate: select customer0_.customer_id as customer1_0_0_, customer0_.customer_name as customer2_0_0_ from hibernate.customer customer0_ where customer0_.customer_id=?
總結:可以發現,采用的是懶加載機制,即獲取到的 1 的一端的對象是一個代理對象。只有在使用這個對象的屬性的情況下,才會發送 SQL 語句。
③ 由懶加載機制引發的 懶加載異常。
@Test public void testMany2OneGet() { Order order = (Order) session.get(Order.class, 1); System.out.println(order.getCustomer().getClass().getName()); session.close(); order.getCustomer().getCustomerName(); }
org.hibernate.LazyInitializationException: could not initialize proxy - no Session
在需要使用對象之前,關閉了 Session 連接,由此會引發 LazyInitializationException 異常。
2.雙向的多對一
(1)還是以 Order 和 Customer 為例:雙向的多對一不僅僅要在 Order 類中定義一個 Customer 屬性,而在 Customer 類中也需定義存放 Order 對象的集合屬性。
(2)創建 Order 和 Customer 表和創建單向多對一相同。
(3)通過 Intellij Idea 生成簡單的持久化類和 Entity.hbm.xml 文件。手動的去建立關聯關系。
<1>生成簡單的持久化類 和 Entity.hbm.xml 文件

package com.nucsoft.hibernate; /** * @author solverpeng * @create 2016-10-11-13:23 */ public class Customer { private Integer customerId; private String customerName; public Integer getCustomerId() { return customerId; } public void setCustomerId(Integer customerId) { this.customerId = customerId; } public String getCustomerName() { return customerName; } public void setCustomerName(String customerName) { this.customerName = customerName; } @Override public boolean equals(Object o) { if(this == o) { return true; } if(o == null || getClass() != o.getClass()) { return false; } Customer customer = (Customer) o; if(customerId != null ? !customerId.equals(customer.customerId) : customer.customerId != null) { return false; } if(customerName != null ? !customerName.equals(customer.customerName) : customer.customerName != null) { return false; } return true; } @Override public int hashCode() { int result = customerId != null ? customerId.hashCode() : 0; result = 31 * result + (customerName != null ? customerName.hashCode() : 0); return result; } }

package com.nucsoft.hibernate; /** * @author solverpeng * @create 2016-10-11-13:23 */ public class Order { private Integer orderId; private String orderName; public Integer getOrderId() { return orderId; } public void setOrderId(Integer orderId) { this.orderId = orderId; } public String getOrderName() { return orderName; } public void setOrderName(String orderName) { this.orderName = orderName; } @Override public boolean equals(Object o) { if(this == o) { return true; } if(o == null || getClass() != o.getClass()) { return false; } Order order = (Order) o; if(orderId != null ? !orderId.equals(order.orderId) : order.orderId != null) { return false; } if(orderName != null ? !orderName.equals(order.orderName) : order.orderName != null) { return false; } return true; } @Override public int hashCode() { int result = orderId != null ? orderId.hashCode() : 0; result = 31 * result + (orderName != null ? orderName.hashCode() : 0); return result; } }

<?xml version='1.0' encoding='utf-8'?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="com.nucsoft.hibernate.Customer" table="customer" schema="hibernate"> <id name="customerId"> <column name="customer_id" sql-type="int(11)"/> </id> <property name="customerName"> <column name="customer_name" sql-type="varchar(50)" length="50" not-null="true"/> </property> </class> </hibernate-mapping>

<?xml version='1.0' encoding='utf-8'?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="com.nucsoft.hibernate.Order" table="order" schema="hibernate"> <id name="orderId"> <column name="order_id" sql-type="int(11)"/> </id> <property name="orderName"> <column name="order_name" sql-type="varchar(50)" length="50" not-null="true"/> </property> </class> </hibernate-mapping>
<2>手動建立關聯關系
①在 Order 一端建立多對一的關聯關系。
- 在 Order 持久化類中添加 Customer 類型的一個屬性 customer。
- 在 Order.hbm.xml 文件中添加多對一的關聯關系。同時修改主鍵生成方式為 native。
②在 Customer 一端建立一對多的關聯關系。
- 在 Customer 持久化類中添加 Order 的一個集合 orders。
- 在 Customer.hbm.xml 添加一對多的關聯關系。同時修改主鍵生成方式為 native。
③詳細說明:在 Customer.hbm.xml 文件中添加一對多的關聯關系。
- 當 Session 從數據庫中加載 Java 集合時,創建的是 Hibernate 內置的集合類的實例。因此在持久化類中定義集合屬性時需要定義成接口類型,不能是具體的某個實現類。
- Hibernate 內置的集合具有集合代理功能,因為有代理功能,所以支持延遲檢索策略。
- 在定義集合的時候,通常將其初始化為集合實現類的一個實例,防止 NullPointerException。
- Hibernate 使用 <set> 元素來映射 Set 類型的屬性。
- 1 的一端的 Set 類型屬性數據還是存放在 n 的一端。
④ set 元素
- name 屬性:待映射的 Set 類型的屬性的屬性名稱。
- table 屬性:待映射的 Set 屬性的泛型類型所對應的表。
- key 子元素:column 屬性,多的一端的外鍵名稱。
- one-to-many 子元素:class 屬性,n 的一端的持久化類名稱。
對應關系如圖。
⑤最終的實體類和 Entity.hbm.xml 文件。

package com.nucsoft.hibernate; import java.util.HashSet; import java.util.Set; /** * @author solverpeng * @create 2016-10-11-13:23 */ public class Customer { private Integer customerId; private String customerName; private Set<Order> orders = new HashSet<>(); public Set<Order> getOrders() { return orders; } public void setOrders(Set<Order> orders) { this.orders = orders; } public Integer getCustomerId() { return customerId; } public void setCustomerId(Integer customerId) { this.customerId = customerId; } public String getCustomerName() { return customerName; } public void setCustomerName(String customerName) { this.customerName = customerName; } @Override public boolean equals(Object o) { if(this == o) { return true; } if(o == null || getClass() != o.getClass()) { return false; } Customer customer = (Customer) o; if(customerId != null ? !customerId.equals(customer.customerId) : customer.customerId != null) { return false; } if(customerName != null ? !customerName.equals(customer.customerName) : customer.customerName != null) { return false; } return true; } @Override public int hashCode() { int result = customerId != null ? customerId.hashCode() : 0; result = 31 * result + (customerName != null ? customerName.hashCode() : 0); return result; } }

package com.nucsoft.hibernate; /** * @author solverpeng * @create 2016-10-11-13:23 */ public class Order { private Integer orderId; private String orderName; private Customer customer; public Customer getCustomer() { return customer; } public void setCustomer(Customer customer) { this.customer = customer; } public Integer getOrderId() { return orderId; } public void setOrderId(Integer orderId) { this.orderId = orderId; } public String getOrderName() { return orderName; } public void setOrderName(String orderName) { this.orderName = orderName; } @Override public boolean equals(Object o) { if(this == o) { return true; } if(o == null || getClass() != o.getClass()) { return false; } Order order = (Order) o; if(orderId != null ? !orderId.equals(order.orderId) : order.orderId != null) { return false; } if(orderName != null ? !orderName.equals(order.orderName) : order.orderName != null) { return false; } return true; } @Override public int hashCode() { int result = orderId != null ? orderId.hashCode() : 0; result = 31 * result + (orderName != null ? orderName.hashCode() : 0); return result; } }

<?xml version='1.0' encoding='utf-8'?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="com.nucsoft.hibernate"> <class name="Customer" table="customer" schema="hibernate"> <id name="customerId"> <column name="customer_id" sql-type="int(11)"/> <generator class="native"/> </id> <property name="customerName"> <column name="customer_name" sql-type="varchar(50)" length="50" not-null="true"/> </property> <set name="orders" table="order"> <key column="customer_id"/> <one-to-many class="Order"/> </set> </class> </hibernate-mapping>

<?xml version='1.0' encoding='utf-8'?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="com.nucsoft.hibernate"> <class name="Order" table="order" schema="hibernate"> <id name="orderId"> <column name="order_id" sql-type="int(11)"/> <generator class="native"/> </id> <property name="orderName"> <column name="order_name" sql-type="varchar(50)" length="50" not-null="true"/> </property> <many-to-one name="customer" class="Customer" column="customer_id"/> </class> </hibernate-mapping>
(4)通過 Intellij Idea 直接生成雙向的多對一的關聯關系。
<1>為生成的每個 Entity.hbm.xml 文件添加主鍵生成方式。
<2>為 Customer 類中的 orders 屬性進行初始化。
<3>最終的持久化類和 Entity.hbm.xml。

package com.nucsoft.hibernate; import java.util.HashSet; import java.util.Set; /** * @author solverpeng * @create 2016-10-11-14:01 */ public class Customer { private Integer customerId; private String customerName; private Set<Order> orders = new HashSet<>(); public Integer getCustomerId() { return customerId; } public void setCustomerId(Integer customerId) { this.customerId = customerId; } public String getCustomerName() { return customerName; } public void setCustomerName(String customerName) { this.customerName = customerName; } @Override public boolean equals(Object o) { if(this == o) { return true; } if(o == null || getClass() != o.getClass()) { return false; } Customer customer = (Customer) o; if(customerId != null ? !customerId.equals(customer.customerId) : customer.customerId != null) { return false; } if(customerName != null ? !customerName.equals(customer.customerName) : customer.customerName != null) { return false; } return true; } @Override public int hashCode() { int result = customerId != null ? customerId.hashCode() : 0; result = 31 * result + (customerName != null ? customerName.hashCode() : 0); return result; } public Set<Order> getOrders() { return orders; } public void setOrders(Set<Order> orders) { this.orders = orders; } }

package com.nucsoft.hibernate; /** * @author solverpeng * @create 2016-10-11-14:01 */ public class Order { private Integer orderId; private String orderName; private Customer customer; public Integer getOrderId() { return orderId; } public void setOrderId(Integer orderId) { this.orderId = orderId; } public String getOrderName() { return orderName; } public void setOrderName(String orderName) { this.orderName = orderName; } @Override public boolean equals(Object o) { if(this == o) { return true; } if(o == null || getClass() != o.getClass()) { return false; } Order order = (Order) o; if(orderId != null ? !orderId.equals(order.orderId) : order.orderId != null) { return false; } if(orderName != null ? !orderName.equals(order.orderName) : order.orderName != null) { return false; } return true; } @Override public int hashCode() { int result = orderId != null ? orderId.hashCode() : 0; result = 31 * result + (orderName != null ? orderName.hashCode() : 0); return result; } public Customer getCustomer() { return customer; } public void setCustomer(Customer customer) { this.customer = customer; } }

<?xml version='1.0' encoding='utf-8'?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="com.nucsoft.hibernate.Customer" table="customer" schema="hibernate"> <id name="customerId"> <column name="customer_id" sql-type="int(11)"/> <generator class="native"/> </id> <property name="customerName"> <column name="customer_name" sql-type="varchar(50)" length="50" not-null="true"/> </property> <set name="orders" inverse="true"> <key> <column name="customer_id" not-null="true"/> </key> <one-to-many not-found="ignore" class="com.nucsoft.hibernate.Order"/> </set> </class> </hibernate-mapping>

<?xml version='1.0' encoding='utf-8'?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="com.nucsoft.hibernate.Order" table="order" schema="hibernate"> <id name="orderId"> <column name="order_id" sql-type="int(11)"/> <generator class="native"/> </id> <property name="orderName"> <column name="order_name" sql-type="varchar(50)" length="50" not-null="true"/> </property> <many-to-one name="customer" class="com.nucsoft.hibernate.Customer"> <column name="customer_id" not-null="true"/> </many-to-one> </class> </hibernate-mapping>
<4>對比發現,通過 Intellij Idea 自動生成的 Customer.hbm.xml 文件中 set 元素多了一個 inverse 屬性。稍后進行說明。
(5)雙向多對一的 CRUD 和需要注意的問題
<1>新增
①雙方都維護關聯關系,即沒有設置 inverse 屬性,且沒有添加非空約束。
先保存 1 的一端,再保存 n 的一端。

@Test public void testMany2OneBothSave() { Customer customer = new Customer(); customer.setCustomerName("aa"); Order order = new Order(); order.setOrderName("order1"); order.setCustomer(customer); Order order2 = new Order(); order2.setOrderName("order2"); order2.setCustomer(customer); customer.getOrders().add(order); customer.getOrders().add(order2); session.save(customer); session.save(order); session.save(order2); }
打印SQL:

Hibernate: insert into hibernate.customer (customer_name) values (?) Hibernate: insert into hibernate.order (order_name, customer_id) values (?, ?) Hibernate: insert into hibernate.order (order_name, customer_id) values (?, ?) Hibernate: update hibernate.order set customer_id=? where order_id=? Hibernate: update hibernate.order set customer_id=? where order_id=?
結果:打印了 3 條 INSERT 語句,2 條 UPDATE 語句
先保存 n 的一端,再保存 1 的一端。

@Test public void testMany2OneBothSave() { Customer customer = new Customer(); customer.setCustomerName("cc"); Order order = new Order(); order.setOrderName("order5"); order.setCustomer(customer); Order order2 = new Order(); order2.setOrderName("order6"); order2.setCustomer(customer); customer.getOrders().add(order); customer.getOrders().add(order2); session.save(order); session.save(order2); session.save(customer); }
打印 SQL :

Hibernate: insert into hibernate.order (order_name, customer_id) values (?, ?) Hibernate: insert into hibernate.order (order_name, customer_id) values (?, ?) Hibernate: insert into hibernate.customer (customer_name) values (?) Hibernate: update hibernate.order set order_name=?, customer_id=? where order_id=? Hibernate: update hibernate.order set order_name=?, customer_id=? where order_id=? Hibernate: update hibernate.order set customer_id=? where order_id=? Hibernate: update hibernate.order set customer_id=? where order_id=?
結果:打印了 3 條 INSERT 語句,4 條 UPDATE 語句。原因,雙方都維護這關聯關系。
②雙方都維護關聯關系,即沒有設置 inverse 屬性,對 order 表中的 customer_id 列添加非空約束(需要更改兩個地方)。
先保存 n 的一端,再保存 1 的一端,會拋出異常。
org.hibernate.TransientPropertyValueException: Not-null property references a transient value - transient instance must be saved before current operation : com.nucsoft.hibernate.Order.customer -> com.nucsoft.hibernate.Customer
③ 1 的一端放棄維護關聯關系,只由 n 的一端來維護。即設置 Customer.hbm.xml 的 set 元素 inverse 屬性值為 true。
先保存 1 的一端,后保存 n 的一端。

Hibernate: insert into hibernate.customer (customer_name) values (?) Hibernate: insert into hibernate.order (order_name, customer_id) values (?, ?) Hibernate: insert into hibernate.order (order_name, customer_id) values (?, ?)
結果:只會發送3條 INSERT 語句。
④總結:
介紹了雙向的多對一的下的保存操作,若都維護關聯關系,則會多出 UPDATE 語句。且若外鍵存在非空約束時,不能先保存 n 的一端。
所以在進行 Hibernate 雙向多對一保存的時候,最好的做法就是:
1 的一端放棄維護關聯關系,即 設置 set 節點的 inverse 屬性為 true。同時在保存的時候先保存 1 的一端,后保存 n 的一端。
<2>刪除

@Test public void testMany2OneBothDelete() { Customer customer = (Customer) session.get(Customer.class, 5); session.delete(customer); }
同刪除單向的多對一相同,會拋出異常:
com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Cannot delete or update a parent row: a foreign key constraint fails
存在外鍵約束。
<3>更新

@Test public void testMany2OneBothUpdate() { Customer customer = (Customer) session.get(Customer.class, 5); System.out.println(customer.getOrders().iterator().next().getOrderName()); customer.getOrders().iterator().next().setOrderName("order@@"); }

Hibernate: select customer0_.customer_id as customer1_0_0_, customer0_.customer_name as customer2_0_0_ from hibernate.customer customer0_ where customer0_.customer_id=? Hibernate: select orders0_.customer_id as customer3_0_0_, orders0_.order_id as order_id1_1_0_, orders0_.order_id as order_id1_1_1_, orders0_.order_name as order_na2_1_1_, orders0_.customer_id as customer3_1_1_ from hibernate.order orders0_ where orders0_.customer_id=? order4 Hibernate: update hibernate.order set order_name=?, customer_id=? where order_id=?
<4>查詢
@Test public void testMany2OneBothGet() { Customer customer = (Customer) session.get(Customer.class, 5); System.out.println(customer.getOrders().getClass()); }
打印結果:
Hibernate: select customer0_.customer_id as customer1_0_0_, customer0_.customer_name as customer2_0_0_ from hibernate.customer customer0_ where customer0_.customer_id=? class org.hibernate.collection.internal.PersistentSet
並沒有查詢關聯的 Order 集合,實際類型為 Hibernate 內置的一個 Set 實現類。
證明了:
當 Session 從數據庫中加載 Java 集合時,創建的是 Hibernate 內置的集合類的實例。因此在持久化類中定義集合屬性時需要定義成接口類型,不能是具體的某個實現類。
也證明了:
Hibernate 內置的集合具有集合代理功能,因為有代理功能,所以支持延遲檢索策略。
<5>說明
這里沒有介紹 cascade 屬性,是因為在實際的項目中,為了保護數據,很少設置 cascade 屬性,而是手動去處理。
二、映射一對一關聯關系。
1.這里只介紹雙向的一對一關聯關系如何映射。單向的一對一只需要把沒有外鍵的一端去掉就好了。
2.基於外鍵映射的雙向一對一
(1)以 Dempartment 和 Manager 為例。一個部門只能有一個經理,一個經理職能管理一個部門。
(2)創建 Department 和 Manager 表。在 Department 表建立 Manager 表的外鍵。

CREATE TABLE department ( dept_id INT(11) PRIMARY KEY NOT NULL AUTO_INCREMENT, dept_name VARCHAR(50), manager_id_fk INT(11) );

CREATE TABLE manager ( manager_id INT(11) PRIMARY KEY NOT NULL AUTO_INCREMENT, manager_name VARCHAR(50) );
(3)通過 Intellij Idea 自動生成的雙向 1 對 1 無法映射列。這里通過 Intellij Idea 生成簡單的持久化類和 Entity.hbm.xml 文件,然后手動的添加映射。

package com.nucsoft.hibernate; /** * @author solverpeng * @create 2016-10-12-9:59 */ public class Department { private Integer deptId; private String deptName; private Manager manager; public Integer getDeptId() { return deptId; } public void setDeptId(Integer deptId) { this.deptId = deptId; } public String getDeptName() { return deptName; } public void setDeptName(String deptName) { this.deptName = deptName; } @Override public boolean equals(Object o) { if(this == o) { return true; } if(o == null || getClass() != o.getClass()) { return false; } Department that = (Department) o; if(deptId != null ? !deptId.equals(that.deptId) : that.deptId != null) { return false; } if(deptName != null ? !deptName.equals(that.deptName) : that.deptName != null) { return false; } return true; } @Override public int hashCode() { int result = deptId != null ? deptId.hashCode() : 0; result = 31 * result + (deptName != null ? deptName.hashCode() : 0); return result; } public Manager getManager() { return manager; } public void setManager(Manager manager) { this.manager = manager; } }

package com.nucsoft.hibernate; /** * @author solverpeng * @create 2016-10-12-9:59 */ public class Manager { private Integer managerId; private String managerName; private Department dept; public Integer getManagerId() { return managerId; } public void setManagerId(Integer managerId) { this.managerId = managerId; } public String getManagerName() { return managerName; } public void setManagerName(String managerName) { this.managerName = managerName; } @Override public boolean equals(Object o) { if(this == o) { return true; } if(o == null || getClass() != o.getClass()) { return false; } Manager manager = (Manager) o; if(managerId != null ? !managerId.equals(manager.managerId) : manager.managerId != null) { return false; } if(managerName != null ? !managerName.equals(manager.managerName) : manager.managerName != null) { return false; } return true; } @Override public int hashCode() { int result = managerId != null ? managerId.hashCode() : 0; result = 31 * result + (managerName != null ? managerName.hashCode() : 0); return result; } public Department getDept() { return dept; } public void setDept(Department dept) { this.dept = dept; } }

<?xml version='1.0' encoding='utf-8'?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="com.nucsoft.hibernate.Department" table="department" schema="hibernate"> <id name="deptId" column="dept_id"> <generator class="native"/> </id> <property name="deptName" column="dept_name"/> <many-to-one name="manager" class="com.nucsoft.hibernate.Manager" column="manager_id_fk" unique="true"/> </class> </hibernate-mapping>

<?xml version='1.0' encoding='utf-8'?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd"> <hibernate-mapping> <class name="com.nucsoft.hibernate.Manager" table="manager" schema="hibernate"> <id name="managerId" column="manager_id"> <generator class="native"/> </id> <property name="managerName" column="manager_name"/> <one-to-one name="dept" class="com.nucsoft.hibernate.Department" property-ref="manager"/> </class> </hibernate-mapping>
(4)說明:
在 department 表中來維護外鍵。在映射的時候選擇 <many-to-one > 元素,通過 unique 屬性來達到 一對一的效果。
在 manager 表中沒有維護外鍵。在映射時候選擇 <one-to-one>元素,至於屬性 property-ref 稍后在查詢的時候說明。
(5)CRUD 以及需要注意的地方。
<1>save

@Test public void testSave() { Department department = new Department(); department.setDeptName("dept-4"); Manager manager = new Manager(); manager.setManagerName("manager=DD"); department.setManager(manager); manager.setDept(department); session.save(manager); session.save(department); }

Hibernate: insert into hibernate.manager (manager_name) values (?) Hibernate: insert into hibernate.department (dept_name, manager_id_fk) values (?, ?)

@Test public void testSave() { Department department = new Department(); department.setDeptName("dept-2"); Manager manager = new Manager(); manager.setManagerName("manager=BB"); department.setManager(manager); manager.setDept(department); session.save(department); session.save(manager); }

Hibernate: insert into hibernate.department (dept_name, manager_id_fk) values (?, ?) Hibernate: insert into hibernate.manager (manager_name) values (?) Hibernate: update hibernate.department set dept_name=?, manager_id_fk=? where dept_id=?
對比發現,先保存沒有外鍵列的對象,會減少 UPDATE 語句的發送,提高性能。
<2>delete

@Test public void testDelete() { Department department = (Department) session.get(Department.class, 4); session.delete(department); }

Hibernate: select department0_.dept_id as dept1_0_0_, department0_.dept_name as dept2_0_0_, department0_.manager_id_fk as manager3_0_0_ from hibernate.department department0_ where department0_.dept_id=? Hibernate: delete from hibernate.department where dept_id=?

@Test public void testDelete() { Manager manager = (Manager) session.get(Manager.class, 5); session.delete(manager); }
Cannot delete or update a parent row: a foreign key constraint fails (`hibernate`.`department`, CONSTRAINT `FK_kpcmf8csabfn9epikikcfqbk0` FOREIGN KEY (`manager_id_fk`) REFERENCES `manager` (`manager_id`))
外鍵關聯對象存在的情況下,不能先刪除擁有外鍵的對象。
<3>update

@Test public void testUpdate() { Department department = (Department) session.get(Department.class, 6); department.getManager().setManagerName("@@"); }

Hibernate: select department0_.dept_id as dept1_0_0_, department0_.dept_name as dept2_0_0_, department0_.manager_id_fk as manager3_0_0_ from hibernate.department department0_ where department0_.dept_id=? Hibernate: select manager0_.manager_id as manager1_1_1_, manager0_.manager_name as manager2_1_1_, department1_.dept_id as dept1_0_0_, department1_.dept_name as dept2_0_0_, department1_.manager_id_fk as manager3_0_0_ from hibernate.manager manager0_ left outer join hibernate.department department1_ on manager0_.manager_id=department1_.dept_id where manager0_.manager_id=? Hibernate: update hibernate.manager set manager_name=? where manager_id=?
<4>get

@Test public void testGet() { Department department = (Department) session.get(Department.class, 6); System.out.println(department); System.out.println(department.getManager().getClass()); System.out.println("---------------"); System.out.println(department.getManager().getManagerName()); }

Hibernate: select department0_.dept_id as dept1_0_0_, department0_.dept_name as dept2_0_0_, department0_.manager_id_fk as manager3_0_0_ from hibernate.department department0_ where department0_.dept_id=? com.nucsoft.hibernate.Department@b0688765 class com.nucsoft.hibernate.Manager_$$_javassist_1 --------------- Hibernate: select manager0_.manager_id as manager1_1_1_, manager0_.manager_name as manager2_1_1_, department1_.dept_id as dept1_0_0_, department1_.dept_name as dept2_0_0_, department1_.manager_id_fk as manager3_0_0_ from hibernate.manager manager0_ left outer join hibernate.department department1_ on manager0_.manager_id=department1_.dept_id where manager0_.manager_id=? @@
可以看到, 查詢擁有外鍵對象關聯的對象時,采用的還是懶加載機制。此種情況下,若 session 關閉,再去調用關聯對象的某個屬性,會發生懶加載異常。
查詢雙向一對一中沒有外鍵的一端:
<one-to-one name="dept" class="com.nucsoft.hibernate.Department"/>

@Test public void testGet2() { Manager manager = (Manager) session.get(Manager.class, 6); System.out.println(manager); }
Hibernate: select manager0_.manager_id as manager1_1_1_, manager0_.manager_name as manager2_1_1_, department1_.dept_id as dept1_0_0_, department1_.dept_name as dept2_0_0_, department1_.manager_id_fk as manager3_0_0_ from hibernate.manager manager0_ left outer join hibernate.department department1_ on manager0_.manager_id=department1_.dept_id where manager0_.manager_id=? com.nucsoft.hibernate.Manager@8ba
<one-to-one name="dept" class="com.nucsoft.hibernate.Department" property-ref="manager"/>
Hibernate: select manager0_.manager_id as manager1_1_1_, manager0_.manager_name as manager2_1_1_, department1_.dept_id as dept1_0_0_, department1_.dept_name as dept2_0_0_, department1_.manager_id_fk as manager3_0_0_ from hibernate.manager manager0_ left outer join hibernate.department department1_ on manager0_.manager_id=department1_.manager_id_fk where manager0_.manager_id=? com.nucsoft.hibernate.Manager@8ba
可以發現,在第一次查詢時,沒有設置 property-ref 屬性。左外鏈接查詢時,雖然結果正確,但是連接條件不正確。
至於說,為什么查詢 Manager 對象的時候,使用了左外鏈接而不是懶加載,因為 Manager 端沒有 Deparment 的外鍵。它不知道誰與它有關系。只能通過左外鏈接查詢一次查詢。
3.基於主鍵的雙向的一對一
(1)本質上和基於外鍵的雙向一對一關聯一樣,只不過是以主鍵作為了外鍵來使用的。
(2)還是以 Department 和 Manager 為例。
(3)deparment 表做了改動,因為是基於主鍵的映射,這里將 department 表中 manager_id_fk 去掉了。
(4)Department.hbm.xml 和 Manager.hbm.xml 文件
<hibernate-mapping> <class name="com.nucsoft.hibernate.Department" table="department" schema="hibernate"> <id name="deptId" column="dept_id"> <generator class="foreign"> <param name="property">manager</param> </generator> </id> <property name="deptName" column="dept_name"/> <one-to-one name="manager" class="com.nucsoft.hibernate.Manager" constrained="true"/> </class> </hibernate-mapping>
對於 Department.hbm.xml 文件,主鍵生成方式改為 foreign,並且指定了主鍵生成所依賴的屬性所對應的持久化類的主鍵生成方式。
需要注意的是,需要在 <one-to-one>節點添加 constrained 屬性為true。
<hibernate-mapping> <class name="com.nucsoft.hibernate.Manager" table="manager" schema="hibernate"> <id name="managerId" column="manager_id"> <generator class="native"/> </id> <property name="managerName" column="manager_name"/> <one-to-one name="dept" class="com.nucsoft.hibernate.Department"/> </class> </hibernate-mapping>
將 Manager.hbm.xml 文件的 <one-to-one> 節點的 property-ref 屬性去掉了。
(5)CRUD及需要注意的問題
<1>save

@Test public void testSave() { Department department = new Department(); department.setDeptName("dept=1"); Manager manager = new Manager(); manager.setManagerName("manager-1"); manager.setDept(department); department.setManager(manager); session.save(manager); session.save(department); }

@Test public void testSave() { Department department = new Department(); department.setDeptName("dept=2"); Manager manager = new Manager(); manager.setManagerName("manager-2"); manager.setDept(department); department.setManager(manager); session.save(department); session.save(manager); }

Hibernate: insert into hibernate.manager (manager_name) values (?) Hibernate: insert into hibernate.department (dept_name, dept_id) values (?, ?)
結論:
發現不論是先保存 department ,還是先保存 manager,都是先插入的 manager。因為 department 的主鍵生成方式是依賴於 manager 的。
<2>update

@Test public void testUpdate() { Department department = (Department) session.get(Department.class, 1); System.out.println(department.getManager().getManagerName()); department.getManager().setManagerName("@@"); }

Hibernate: select department0_.dept_id as dept1_0_0_, department0_.dept_name as dept2_0_0_ from hibernate.department department0_ where department0_.dept_id=? Hibernate: select manager0_.manager_id as manager1_1_1_, manager0_.manager_name as manager2_1_1_, department1_.dept_id as dept1_0_0_, department1_.dept_name as dept2_0_0_ from hibernate.manager manager0_ left outer join hibernate.department department1_ on manager0_.manager_id=department1_.dept_id where manager0_.manager_id=? manager-1 Hibernate: update hibernate.manager set manager_name=? where manager_id=?

@Test public void testUpdate() { Manager manager = (Manager) session.get(Manager.class, 1); System.out.println(manager.getDept().getDeptName()); manager.getDept().setDeptName("@@Dept"); }

Hibernate: select manager0_.manager_id as manager1_1_1_, manager0_.manager_name as manager2_1_1_, department1_.dept_id as dept1_0_0_, department1_.dept_name as dept2_0_0_ from hibernate.manager manager0_ left outer join hibernate.department department1_ on manager0_.manager_id=department1_.dept_id where manager0_.manager_id=? dept=1 Hibernate: update hibernate.department set dept_name=? where dept_id=?
<3>get

@Test public void testGet() { Department department = (Department) session.get(Department.class, 1); System.out.println(department.getManager().getClass()); System.out.println("--------------------------------------"); Manager manager = (Manager) session.get(Manager.class, 1); }

Hibernate: select department0_.dept_id as dept1_0_0_, department0_.dept_name as dept2_0_0_ from hibernate.department department0_ where department0_.dept_id=? class com.nucsoft.hibernate.Manager_$$_javassist_1 -------------------------------------- Hibernate: select manager0_.manager_id as manager1_1_1_, manager0_.manager_name as manager2_1_1_, department1_.dept_id as dept1_0_0_, department1_.dept_name as dept2_0_0_ from hibernate.manager manager0_ left outer join hibernate.department department1_ on manager0_.manager_id=department1_.dept_id where manager0_.manager_id=?
通過 department 獲取到的 manager 采用的是懶加載機制。而從 manager 獲取 dept ,是通過左外鏈接查詢的。
至於原因,第一小點已經提到,本質上和基於外鍵的雙向一對一關聯一樣,只不過是以主鍵作為了外鍵來使用的。
<4>delete

@Test public void testDelete() { Manager manager = (Manager) session.get(Manager.class, 1); session.delete(manager); }
Cannot delete or update a parent row: a foreign key constraint fails (`hibernate`.`department`, CONSTRAINT `FK_8hf3vewo7w3v9doungcc51wwy` FOREIGN KEY (`dept_id`) REFERENCES `manager` (`manager_id`))
若先刪除 manager ,且存在和此 manager 關聯的 department ,需要先刪除關聯的 department 記錄。
三、映射 n 對 n 關聯關系
1.單向的 n 對 n 關聯
(1)必須使用中間表
(2)以 Category 和 Item 為例。一個分類下可以有多個商品,一個商品可以屬於多個分類。以 Category 下存在 Set<Item> 集合為例來測試單向的 n 對 n 映射。
(3)創建 category、item、categories_items 表

CREATE TABLE category ( category_id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, category_name VARCHAR(50) ); CREATE TABLE item ( item_id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, item_name VARCHAR(50) ); CREATE TABLE categories_items ( category_id INT(11) NOT NULL, item_id INT(11) NOT NULL );
(4)通過 Intellij Idea 生成持久化類和 Entity.hbm.xml 文件
生成的持久化類和 Category.hbm.xml 和 Item.hbm.xml 文件。

public class Category { private Integer categoryId; private String categoryName; private Set<Item> items; }

public class Item { private Integer itemId; private String itemName; }

<hibernate-mapping> <class name="com.nucsoft.hibernate.Item" table="item" schema="hibernate"> <id name="itemId" column="item_id"> <generator class="native"/> </id> <property name="itemName" column="item_name"/> </class> </hibernate-mapping>
<hibernate-mapping> <class name="com.nucsoft.hibernate.Category" table="category" schema="hibernate"> <id name="categoryId" column="category_id"> <generator class="native"/> </id> <property name="categoryName" column="category_name"/> <!-- table 指中間表 --> <set name="items" table="categories_items" schema="hibernate"> <key> <!-- category 在中間表中的列名 --> <column name="category_id"/> </key> <!-- class Set 集合中持久化類的類名, column Set集合中的持久化類咋中間表的外鍵列的名稱 --> <many-to-many not-found="ignore" class="com.nucsoft.hibernate.Item"> <column name="item_id"/> </many-to-many> </set> </class> </hibernate-mapping>
注釋已經講的很明白了。
(5)CRUD 以及需要注意的地方。
<1>save

@Test public void testSave() { Item item = new Item(); item.setItemName("item-aa"); Item item2 = new Item(); item2.setItemName("item-bb"); Category category = new Category(); category.setCategoryName("cate-1"); category.getItems().add(item); category.getItems().add(item2); session.save(item); session.save(item2); session.save(category); }

Hibernate: insert into hibernate.item (item_name) values (?) Hibernate: insert into hibernate.item (item_name) values (?) Hibernate: insert into hibernate.category (category_name) values (?) Hibernate: insert into hibernate.categories_items (category_id, item_id) values (?, ?) Hibernate: insert into hibernate.categories_items (category_id, item_id) values (?, ?)

@Test public void testSave() { Item item = new Item(); item.setItemName("item-cc"); Item item2 = new Item(); item2.setItemName("item-dd"); Category category = new Category(); category.setCategoryName("cate-2"); category.getItems().add(item); category.getItems().add(item2); session.save(category); session.save(item); session.save(item2); }

Hibernate: insert into hibernate.category (category_name) values (?) Hibernate: insert into hibernate.item (item_name) values (?) Hibernate: insert into hibernate.item (item_name) values (?) Hibernate: insert into hibernate.categories_items (category_id, item_id) values (?, ?) Hibernate: insert into hibernate.categories_items (category_id, item_id) values (?, ?)
結論:
對比發現,不論是先保存 Item 還是先保存 Category 對象,都不會多發送 UPDATE 語句,因為最終的關聯關系是通過中間表進行維護的。
<2>get

@Test public void testGet() { Category category = (Category) session.get(Category.class, 1); System.out.println(category.getItems().getClass()); System.out.println(category.getItems().size()); }

Hibernate: select category0_.category_id as category1_1_0_, category0_.category_name as category2_1_0_ from hibernate.category category0_ where category0_.category_id=? class org.hibernate.collection.internal.PersistentSet Hibernate: select items0_.category_id as category1_1_1_, items0_.item_id as item2_0_1_, item1_.item_id as item1_2_0_, item1_.item_name as item2_2_0_ from hibernate.categories_items items0_ inner join hibernate.item item1_ on items0_.item_id=item1_.item_id where items0_.category_id=? 2
還是懶加載,但是查詢關聯的 Items 的時候,內連接關聯了中間表。
<3>delete

@Test public void testDelete() { Category category = (Category) session.get(Category.class, 1); session.delete(category); }
Hibernate: select category0_.category_id as category1_1_0_, category0_.category_name as category2_1_0_ from hibernate.category category0_ where category0_.category_id=? Hibernate: delete from hibernate.categories_items where category_id=? Hibernate: delete from hibernate.category where category_id=?
刪除 category 的時候,先刪除的中間表,然后才刪除的 category表。表明,先解除關系,然后刪除。

@Test public void testDelete2() { Item item = (Item) session.get(Item.class, 4); session.delete(item); }
Cannot delete or update a parent row: a foreign key constraint fails (`hibernate`.`categories_items`, CONSTRAINT `FK_pxxtlg5rb8it8ewp5lxhss3c5` FOREIGN KEY (`item_id`) REFERENCES `item` (`item_id`))
刪除 item 時,因為有關聯關系的存在,且維護關聯關系是由 Category 維護的,所以無法刪除。
2.映射雙向的 n 對 n
(1)還以 Category 和 Item 為例。Category 中擁有 Set<Item> 類型的屬性, Item 中擁有 Set<Category> 類型的屬性。
(2)通過 Intellij Idea 生成持久化類和 Entity.hbm.xml 文件。
對應生成的文件:

public class Category { private Integer categoryId; private String categoryName; private Set<Item> items; }

public class Item { private Integer itemId; private String itemName; private Set<Category> categories; }
<hibernate-mapping package="com.nucsoft.hibernate" schema="hibernate"> <class name="Item" table="item"> <id name="itemId" column="item_id"> <generator class="native"/> </id> <property name="itemName" column="item_name"/> <set name="categories" inverse="true" table="categories_items"> <key> <column name="item_id"/> </key> <!-- 交叉對應 --> <many-to-many not-found="ignore" class="Category"> <column name="category_id"/> </many-to-many> </set> </class> </hibernate-mapping>
<hibernate-mapping package="com.nucsoft.hibernate" schema="hibernate"> <class name="Category" table="category"> <id name="categoryId" column="category_id"> <generator class="native"/> </id> <property name="categoryName" column="category_name"/> <set name="items" table="categories_items"> <key> <column name="category_id"/> </key> <!-- 交叉對應 --> <many-to-many not-found="ignore" class="Item"> <column name="item_id"/> </many-to-many> </set> </class> </hibernate-mapping>
(3)注意:
- 需要兩端都使用集合屬性
- 必須使用中間表
- 兩邊都需要指定中間表表名以及在中間表中外鍵列的列名。
- 必須把其中一端 的 inverse 屬性設置為 true
四、總結
介紹了Hibernate如何映射 單雙向的多對一,基於外鍵、基於主鍵的一對一,以及單雙向的多對多的關聯關系。包括在 Intellij Idea下如何操作,以及各個類型映射下的 CRUR 以及需要注意的地方。
沒有介紹 Hibernate 是如何映射繼承關系,是因為在真實的生產環境下還沒有遇到這種情況,這里不做說明。
文章篇幅較長,所以摘錄了一個附錄出來,以便查詢方便。http://www.cnblogs.com/solverpeng/p/5953536.html
寫文章不易,若轉載,請標明出處。
注意:每次通過 Intellij Idea 的 Database Schema 生成 Entity.hbm.xml 和持久化類的時候,都會覆蓋數據庫連接的 username 和 password 。需要重新添加。