一、映射組成關係
Hibernate 把持久化類的屬性分爲兩種:
- 值(value)類型: 沒有 OID,不能被單獨持久化,生命週期依賴於所屬的持久化類的對象的生命週期。
- 實體(entity)類型:有 OID,可以被單獨持久化,有獨立的生命週期,無法直接用 property 進行映射。Hibernate 使用
<component>
元素來映射組成關係。
<!--映射組成關係-->
<component name="pay" class="Pay">
<!--指定組成關係的組件的屬性-->
<parent name="worker"/> <!--<用於指定組件屬性所屬的整體類-->
<property name="monthPay" column="monthPay"></property>
<property name="yearPay" column="yearPay"></property>
<property name="vocationWithPay" column="vocationWithPay"></property>
</component>
說明:
① <component>
元素來映射組成關係,其中的class屬性:設定組成關係屬性的類型。在此處表明 pay 屬性爲 Pay 類型。
② <parent>
元素指定組件屬性所屬的整體類,其中的name屬性:整體類在組件類中的屬性名。
二、映射一對多關聯關係
1、單向一對多的關聯關係
以 Customer 和 Order 爲例: 一個用戶能發出多個訂單, 而一個訂單隻能屬於一個客戶。從 Order 到 Customer 的關聯是多對一關聯;而從 Customer 到 Order 是一對多關聯。
- 單向 n-1 關聯只需從 n 的一端可以訪問 1 的一端
- 域模型: 從 Order 到 Customer 的多對一單向關聯需要在Order 類中定義一個 Customer 屬性, 而在 Customer 類中無需定義存放 Order 對象的集合屬性
- Hibernate 使用 元素來映射多對一關聯關係
<many-to-one> 元素來映射組成關係
name: 設定待映射的持久化類的屬性的名字
column: 設定和持久化類的屬性對應的表的外鍵
class:設定待映射的持久化類的屬性的類型
實體類Customer:
public class Customer {
private int customerId;
private String customerName;
public Customer() {
}
//getter/setter方法
}
實體類映射文件Customer.hbm.xml:
<?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.zm.n21.Customer" table="customer">
<id name="customerId" type="java.lang.Integer">
<column name="customer_id"/>
<generator class="native"/>
</id>
<property name="customerName" type="java.lang.String">
<column name="customer_name"/>
</property>
</class>
</hibernate-mapping>
實體類Orders:
public class Orders {
private int orderId;
private String orderName;
//多對一的映射。多端:Orders;一端:Customer
Customer customer;
public Orders() {
}
//getter/setter方法
}
實體類映射文件Orders.hbm.xml:
<?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.zm.n21">
<class name="com.zm.n21.Orders" table="orders">
<id name="orderId" type="java.lang.Integer">
<column name="order_id"/>
<generator class="native"/>
</id>
<property name="orderName" type="java.lang.String">
<column name="order_name"/>
</property>
<!--映射多對一的關聯關係,使用many-to-one來映射多對一的關聯關係
name:多這一端關聯的那一端的屬性的名字
class:一那一端的屬性對應的類名
column:一那一端在多那一端對應的數據表中的外鍵的名字
-->
<many-to-one name="customer" class="Customer" column="customer_id"></many-to-one>
</class>
</hibernate-mapping>
在hibernate.cfg.xml註冊映射文件:
<mapping resource="com/zm/n21/Orders.hbm.xml"/>
<mapping resource="com/zm/n21/Customer.hbm.xml"/>
(1)插入操作的測試
先插入Customer再插入Order:
@Test
public void many2One(){
Customer customer = new Customer();
customer.setCustomerName("AA");
Orders orders1 = new Orders();
orders1.setOrderName("Orders-1");
Orders orders2 = new Orders();
orders2.setOrderName("Orders-2");
//設定關聯關係
orders1.setCustomer(customer);
orders2.setCustomer(customer);
//執行save操作
session.save(customer);
session.save(orders1);
session.save(orders2);
}
3條INSERT語句。總結:先插入1的一端,再插入n的一端,只有INSERT語句
先插入Order再插入Customer:
@Test
public void many2One(){
Customer customer = new Customer();
customer.setCustomerName("BB");
Orders orders1 = new Orders();
orders1.setOrderName("Order-3");
Orders orders2 = new Orders();
orders2.setOrderName("Order-4");
//設定關聯關係
orders1.setCustomer(customer);
orders2.setCustomer(customer);
/*先插入Orders,再插入Customer*/
session.save(orders1);
session.save(orders2);
session.save(customer);
}
3條INSERT語句,2條UPDATE語句。先插入n的一端,再插入1的一端,會多出UPDATE語句。因爲在插入多的一端時,無法確定1的一端的外鍵值,只能等1的一端插入後,再額外發送UPDATE語句。
總的來說,推薦先插入1的一端,再插入n的一端。
(2)多對一單向映射查詢操作時
- 若查詢多的一端的一個對象,默認情況下,只查詢了多的一端的對象,而沒有查詢關聯的1的那端的對象。
- 在需要使用到關聯的對象時,才發送對應的SQL語句
- 在查詢Customer對象時,由多的一端導航到1的一端時,若此時session已經被關閉,則在默認情況下可能會發生懶加載異常(LazyInitializationException)
- 獲取Order對象時,默認情況下,其關聯的Customer對象是一個代理對象
(3)多對一單向映射修改操作時
修改1那一端的數據時,會發送兩個SELECT語句(一個是查詢多端對象,一個是查詢1端對象),然後再發送一個UPDATE語句。
(4)多對一單向映射刪除操作時
在不設定級聯關係的情況下,且1的這一端的對象有n的對象在引用,不能直接刪除1這一端的對象。
三、雙向一對多映射
- 雙向 1-n 與 雙向 n-1 是完全相同的兩種情形
- 雙向 1-n 需要在 1 的一端可以訪問 n 的一端, 反之依然.
- 域模型:從 Order 到 Customer 的多對一雙向關聯需要在Order 類中定義一個 Customer 屬性, 而在 Customer 類中需定義存放 Order 對象的集合屬性
- 關係數據模型:ORDERS 表中的 CUSTOMER_ID 參照 CUSTOMER 表的主鍵
- Hibernate 使用 元素來映射 set 類型的屬性
<!-- set: 映射 set 類型的屬性;name:一的這一端關聯的多的那一端的屬性名;
table: set 中的元素對應的記錄放在哪一個數據表中。該值需要和多對一的多的那個表的名字一致 -->
<set name="orders" table="ORDERS">
<!-- 指定關聯的表中的外鍵列的名字 -->
<key column="CUSTOMER_ID"></key>
<!-- 指定映射類型 -->
<one-to-many class="Order"/>
</set>
幾個注意點:
- 當 Session 從數據庫中加載 Java 集合時,創建的是 Hibernate 內置集合類的實例,因此在持久化類中定義集合屬性時必須把屬性聲明爲 Java 接口類型。例如應該聲明爲Set而不是HashSet。
①Hibernate 的內置集合類具有集合代理功能,支持延遲檢索策略。類似於在單向n-1關係的get操作,如果在雙向1-n的get操作中獲取了customer對象,如果不使用它存放order的集合,那麼這個集合就不會被加載,只有使用到時纔會加載。
② 事實上,Hibernate 的內置集合類封裝了 JDK 中的集合類,這使得 Hibernate 能夠對緩存中的集合對象進行髒檢查,按照集合對象的狀態來同步更新數據庫。 - 在定義集合屬性時,通常把它初始化爲集合實現類的一個實例。這樣可以提高程序的健壯性,避免應用程序訪問取值爲 null 的集合的方法拋出
NullPointerException
。
代碼實例如下:
實體類對象Order:
public class Order {
private Integer orderId;
private String orderName;
private Customer customer;
//getter/setter方法
}
實體類對象Customer:
public class Customer {
private Integer customerId;
private String customerName;
/*注意兩點:
* 1. 聲明集合類型時, 需使用接口類型, 因爲 hibernate 在獲取
* 集合類型時, 返回的是 Hibernate 內置的集合類型, 而不是 JavaSE 一個標準的集合實現.
* 2. 需要把集合進行初始化, 可以防止發生空指針異常
*/
private Set<Order> orders = new HashSet<>();
//getter/setter方法
}
實體類對象對應的映射文件:
Order.hbm.xml:
<?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.zm.n21.both">
<class name="Order" table="ORDERS">
<id name="orderId" type="java.lang.Integer">
<column name="ORDER_ID" />
<generator class="native" />
</id>
<property name="orderName" type="java.lang.String">
<column name="ORDER_NAME" />
</property>
<!--
映射多對一的關聯關係。 使用 many-to-one 來映射多對一的關聯關係
name: 多這一端關聯的一那一端的屬性的名字
class: 一那一端的屬性對應的類名
column: 一那一端在多的一端對應的數據表中的外鍵的名字
-->
<many-to-one name="customer" class="Customer" column="CUSTOMER_ID"></many-to-one>
</class>
</hibernate-mapping>
Customer.hbm.xml:
<?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.zm.n21.both">
<class name="Customer" table="CUSTOMERS">
<id name="customerId" type="java.lang.Integer">
<column name="CUSTOMER_ID" />
<generator class="native" />
</id>
<property name="customerName" type="java.lang.String">
<column name="CUSTOMER_NAME" />
</property>
<!-- 映射 1 對多的那個集合屬性 -->
<!-- set: 映射 set 類型的屬性, name:一的這一端關聯的多的那一端的屬性名,table: set 中的元素對應的記錄放在哪一個數據表中. 該值需要和多對一的多的那個表的名字一致 -->
<set name="orders" table="ORDERS">
<!-- 執行多的表中的外鍵列的名字 -->
<key column="CUSTOMER_ID"></key>
<!-- 指定映射類型 -->
<one-to-many class="Order"/>
</set>
</class>
</hibernate-mapping>
set元素中的3個重要屬性
(1)set元素中的inverse
屬性:
- 指定由哪一方來維護關聯關係。
inverse = false
的爲主動方,inverse = true
的爲被動方。由主動方
負責維護關聯關係通常設置爲true
,以指定由多的一端來維護關聯關係。在沒有設置inverse=true
的情況下,父子兩邊都維護父子。 - 在 1-n 關係中,將 n 方設爲主控方將有助於性能改善關係 。在 1-N 關係中,若將 1 方設爲主控方,會額外多出 update 語句。插入數據時無法同時插入外鍵列,因而無法爲外鍵列添加非空約束。
(2)set元素中的cascade
屬性:
- 在對象 – 關係映射文件中, 用於映射持久化類之間關聯關係的元素,
<set>
、<many-to-one>
和<one-to-one>
都有一個cascade
屬性,它用於指定如何操縱與當前對象關聯的其他對象。
在開發時不建議設定該屬性,建議使用手工的方式來處理。
(3)集合排序屬性
- 元素有一個 order-by 屬性,如果設置了該屬性,當 Hibernate 通過 select 語句到數據庫中檢索集合對象時,利用 order by 子句進行排序
- order-by 屬性中還可以加入 SQL 函數
在hibernate.cfg.xml文件中註冊:
<mapping resource="com/zm/n21/both/Customer.hbm.xml"/>
<mapping resource="com/zm/n21/both/Order.hbm.xml"/>
1、多對1雙向映射保存操作
(1)執行save操作:先插入Customer,再插入Order。3條INSERT,2條UPDATE。因爲1的一端和多額一端都維護關聯關係,所以會多出uPDATE。可以在1的一端的set節點指定inverse=true,來使1的一端放棄維護關聯關係!建議設定set的inverse=true,建議先插入1的一端,後插入多的一端。好處:不會多出UPDATE語句。
@Test
public void testManytoOneSave(){
Customer customer = new Customer();
customer.setCustomerName("AA");
Order order1 = new Order();
order1.setOrderName("Order-1");
Order order2 = new Order();
order2.setOrderName("Order-2");
//設定關聯關係
order1.setCustomer(customer);
order2.setCustomer(customer);
customer.getOrders().add(order1);
customer.getOrders().add(order2);
//執行save操作:先插入Customer,再插入Order
session.save(customer);
session.save(order1);
session.save(order2);
}
(2)執行save操作:先插入Order,再插入Customer。3條INSERT,4條UPDATE。
//執行save操作:先插入Order,再插入Customer
session.save(order1);
session.save(order2);
session.save(customer);
2、雙向1對多的獲取操作
@Test
public void testOneToMany(){
//1. 對n的一端的集合使用延遲加載
Customer customer = (Customer) session.get(Customer.class, 1);
System.out.println(customer.getCustomerName());
//2.返回的多的一端的集合是Hibenrate內置的集合類型,該類型具有延遲加載和存放代理對象的功能
//class org.hibernate.collection.internal.PersistentSet
System.out.println(customer.getOrders().getClass());
//session.close();
//3.可能會拋出懶加載異常LazyInitializationException
//4.需要使用集合中元素的時候進行初始化
}
3、雙向1對多修改操作
四、基於外鍵的1對1關係映射
- 對於基於外鍵的1-1關聯,其外鍵可以存放在任意一邊,在需要存放外鍵的一端,增加
many-to-one
元素。爲many-to-one
元素增加unique=“true”
屬性來表示爲1-1關聯。
<!--使用外鍵的方式來映射1-1關聯關係-->
<many-to-one name="manager" class="com.zm.one2one.foreignkey.Manager" column="manger_id"
unique="true"></many-to-one>
- 另一端需要使用
one-to-one
元素,該元素使用property-ref
屬性指定使用被關聯實體主鍵以外的字段作爲關聯字段。
<!--映射1-1的關聯關係:在對應的數據表中已經有外鍵了,當前持久化類使用one-to-one進行映射-->
<one-to-one name="dept" class="com.zm.one2one.foreignkey.Department" property-ref="manager"></one-to-one>
相關實例如下:
實體類對象Department:
public class Department {
private Integer deptId;
private String deptName;
private Manager manager;
//getxxx/setxxx方法
}
對應的映射文件Department.hbm.xml:
<?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.zm.one2one.foreignkey.Department" table="DEPARTMENTS">
<id name="deptId" type="java.lang.Integer">
<column name="dept_id"/>
<generator class="native"/>
</id>
<property name="deptName" type="java.lang.String">
<column name="dept_name"/>
</property>
<!--使用外鍵的方式來映射1-1關聯關係-->
<many-to-one name="manager" class="com.zm.one2one.foreignkey.Manager" column="manger_id"
unique="true"></many-to-one>
</class>
</hibernate-mapping>
實體類對象Manager:
public class Manager {
private Integer mangerId;
private String managerName;
private Department dept;
//getxxx/setxxx方法
}
對應的映射文件Manager.hbm.xml:
<?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.zm.one2one.foreignkey.Manager" table="MANAGERS">
<id name="mangerId" type="java.lang.Integer">
<column name="manger_id"/>
<generator class="native"/>
</id>
<property name="managerName" type="java.lang.String">
<column name="manager_name"/>
</property>
<!--映射1-1的關聯關係:在對應的數據表中已經有外鍵了,當前持久化類使用one-to-one進行映射
沒有外鍵的一端需要使用one-to-one元素,該元素使用property-ref屬性指定使用被關聯主鍵以外的字段作爲關聯字段-->
<one-to-one name="dept" class="com.zm.one2one.foreignkey.Department" property-ref="manager"></one-to-one>
</class>
</hibernate-mapping>
在hibernate的配置文件中註冊對應的映射文件:
<!--需要關聯的hibernate映射文件.hbm.xml-->
<mapping resource="com/zm/one2one/foreignkey/Department.hbm.xml" />
<mapping resource="com/zm/one2one/foreignkey/Manager.hbm.xml"/>
1、保存操作
@Test
public void testSave(){
Department dept = new Department();
// dept.setDeptName("Dept-AA");
dept.setDeptName("Dept-BB");
Manager manager = new Manager();
// manager.setManagerName("Manager-AA");
manager.setManagerName("Manager-BB");
//設定關聯關係
manager.setDept(dept);
dept.setManager(manager);
//先保存的是沒有外鍵列的對象,再保存有外鍵列的對象。2條INSERT語句,沒有UPDATE語句
// Hibernate: insert into MANAGERS (manager_name) values (?)
// Hibernate: insert into DEPARTMENTS (dept_name, manger_id) values (?, ?)
// session.save(manager);
// session.save(dept);
//先保存有外鍵列對象,在保存無外鍵列對象。2條INSERT語句,1條UPDATE語句
//Hibernate: insert into DEPARTMENTS (dept_name, manger_id) values (?, ?)
//Hibernate: insert into MANAGERS (manager_name) values (?)
//Hibernate: update DEPARTMENTS set dept_name=?, manger_id=? where dept_id=?
session.save(dept);
session.save(manager);
}
總結:
- 先保存沒有外鍵列的對象,再保存有外鍵列的對象:2條INSERT語句,沒有UPDATE語句。先保存有外鍵列對象,在保存無外鍵列對象:2條INSERT語句,1條UPDATE語句
- 推薦先保存無外鍵列的對象,在保存有外鍵列的對象。
2、查詢操作
@Test
public void testGet(){
//1.默認情況下對關聯屬性使用懶加載
//Hibernate: select department0_.dept_id as dept_id1_0_0_, department0_.dept_name as dept_nam2_0_0_,
// department0_.manger_id as manger_i3_0_0_ from DEPARTMENTS department0_ where department0_.dept_id=?
//Dept-AA
Department department = (Department) session.get(Department.class, 1);
System.out.println(department.getDeptName());
//2.關閉session後,再獲取對象的屬性值,會出現懶加載異常LazyInitializationException
// session.close();
// Manager manager = department.getManager();
// System.out.println(manager.getClass());
// System.out.println(manager.getManagerName());
//在one-to-one中沒有設置property-ref屬性的值時,查詢Manager對象的連接條件會出現錯誤,
// 應該是dept.manager_id=manager.manager_id,而不是dept.dept_id=manager.manager_id
//在添加了property-ref的屬性設置時正確
Manager manager = department.getManager();
System.out.println(manager.getManagerName());
}
在查詢沒有外鍵的實體對象時,使用的是左外連接查詢,一併查出其關聯的對象,並且已經初始化。
@Test
public void testGet2(){
//在查詢沒有外鍵的實體對象時,使用的是左外連接查詢,一併查出其關聯的對象,並且已經初始化
Manager manager = (Manager) session.get(Manager.class,1);
System.out.println(manager.getManagerName());
System.out.println(manager.getDept().getDeptName());
}
五、基於主鍵映射的1對1
- 基於主鍵的映射策略:指一端的主鍵生成器使用 foreign 策略,表明根據”
對方
”的主鍵來生成自己的主鍵,自己並不能獨立生成主鍵。 子元素指定使用當前持久化類的哪個屬性作爲 “對方”。
<id name="deptId" type="java.lang.Integer">
<column name="dept_id"/>
<!--使用外鍵的方式生成當前的主鍵-->
<generator class="foreign">
<!--property屬性指定使用當前持久化類的哪一個屬性的主鍵作爲外鍵-->
<param name="property">manager</param>
</generator>
</id>
- 採用foreign主鍵生成器策略的一端增加
one-to-one
元素映射關聯屬性,其one-to-one
屬性還應增加constrained=“true”
屬性;另一端增加one-to-one
元素映射關聯屬性。 - constrained(約束):指定爲當前持久化類對應的數據庫表的主鍵添加一個外鍵約束,引用被關聯的對象(“對方”)所對應的數據庫表主鍵。
<!--採用foreign主鍵生成器策略的一端增加one-to-one元素映射關聯屬性,其one-to-one節點還應
增加constrained="true"屬性,以使當前的主鍵上添加外鍵約束-->
<one-to-one name="manager" class="Manager" constrained="true"/>
實例代碼:
實體類對象Department:
public class Department {
private Integer deptId;
private String deptName;
private Manager manager;
//getxxx/setxxx方法
}
實體類對象Manager:
public class Manager {
private Integer mangerId;
private String managerName;
private Department dept;
//getxxx/setxxx方法
}
對應的映射文件:
Department.hbm.xml:
<?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.zm.one2one.primarykey">
<class name="Department" table="DEPARTMENTS">
<id name="deptId" type="java.lang.Integer">
<column name="dept_id"/>
<!--使用外鍵的方式生成當前的主鍵-->
<generator class="foreign">
<!--property屬性指定使用當前持久化類的哪一個屬性的主鍵作爲外鍵-->
<param name="property">manager</param>
</generator>
</id>
<property name="deptName" type="java.lang.String">
<column name="dept_name"/>
</property>
<!--採用foreign主鍵生成器策略的一端增加one-to-one元素映射關聯屬性,其one-to-one節點還應
增加constrained="true"屬性,以使當前的主鍵上添加外鍵約束-->
<one-to-one name="manager" class="Manager" constrained="true"/>
</class>
</hibernate-mapping>
Manager.hbm.xml:
<?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.zm.one2one.primarykey">
<class name="Manager" table="MANAGERS">
<id name="mangerId" type="java.lang.Integer">
<column name="manger_id"/>
<generator class="native"/>
</id>
<property name="managerName" type="java.lang.String">
<column name="manager_name"/>
</property>
<!--映射1-1的關聯關係:在對應的數據表中已經有外鍵了,當前持久化類使用one-to-one進行映射-->
<one-to-one name="dept" class="Department"></one-to-one>
</class>
</hibernate-mapping>
在hibernate的配置文件中註冊:
<mapping resource="com/zm/one2one/primarykey/Department.hbm.xml" />
<mapping resource="com/zm/one2one/primarykey/Manager.hbm.xml"/>
1、保存操作
不論是否先插入有外鍵列的一端,都只有INSERT語句。
@Test
public void testSave(){
Department dept = new Department();
dept.setDeptName("Dept-AA");
//dept.setDeptName("Dept-BB");
Manager manager = new Manager();
manager.setManagerName("Manager-AA");
//manager.setManagerName("Manager-BB");
//設定關聯關係
dept.setManager(manager);
manager.setDept(dept);
//先插入哪一個都一樣,只有INSERT語句
session.save(manager);
session.save(dept);
// session.save(dept);
// session.save(manager);
}
六、映射多對多關聯關係
1、單向多對多關聯關係
- n-n 的關聯必須使用連接表
- 與 1-n 映射類似,必須爲
set
集合元素添加key
子元素,指定CATEGORIES_ITEMS
表中參照CATEGORIES
表的外鍵爲CATEGORIY_ID
。與 1-n 關聯映射不同的是,建立 n-n 關聯時, 集合中的元素使用many-to-many
,many-to-many 子
元素的class
屬性指定 items 集合中存放的是 Item 對象,column
屬性指定CATEGORIES_ITEMS
表中參照 ITEMS 表的外鍵爲I_ID
<!--table:指定中間表-->
<set name="items" table="CATEGORIES_ITEMS">
<key>
<column name="C_ID"/>
</key>
<!--使用many-to-many來指定多對多的關聯關係,column指定Set集合中的持久化類在中間表的外鍵列的名稱-->
<many-to-many class="com.zm.n2n.Item" column="I_ID"></many-to-many>
</set>
實例代碼:
實體類Category:
package com.zm.n2n;
import java.util.HashSet;
import java.util.Set;
public class Category {
private Integer categoryId;
private String categoryName;
private Set<Item> items = new HashSet<>();
//getxxx/setxxx方法
}
實體類Item:
public class Item {
private Integer id;
private String name;
//getxxx/setxxx方法
}
實體類對應的映射文件:
Category.hbm.xml:
<?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.zm.n2n.Category" table="CATEGORIES">
<id name="categoryId" type="java.lang.Integer">
<column name="category_id"/>
<generator class="native"/>
</id>
<property name="categoryName" type="java.lang.String">
<column name="category_name"/>
</property>
<!--table:指定中間表-->
<set name="items" table="CATEGORIES_ITEMS">
<key>
<column name="C_ID"/>
</key>
<!--使用many-to-many來指定多對多的關聯關係,column指定Set集合中的持久化類在中間表的外鍵列的名稱-->
<many-to-many class="com.zm.n2n.Item" column="I_ID"></many-to-many>
</set>
</class>
</hibernate-mapping>
Item.hbm.xml:
<?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.zm.n2n.Item" table="ITEMS">
<id name="id" type="java.lang.Integer">
<column name="ID"/>
<generator class="native"/>
</id>
<property name="name" type="java.lang.String">
<column name="NAME"/>
</property>
</class>
</hibernate-mapping>
在hibernate的配置文件中註冊映射文件:
<mapping resource="com/zm/n2n/Category.hbm.xml" />
<mapping resource="com/zm/n2n/Item.hbm.xml"/>
2、雙向多對多關聯關係
- 雙向 n-n 關聯需要兩端都使用集合屬性
- 雙向n-n關聯必須使用連接表
- 集合屬性應增加 key 子元素用以映射外鍵列,集合元素裏還應增加
many-to-many
子元素關聯實體類 - 在雙向 n-n 關聯的兩邊都需指定連接表的表名及外鍵列的列名。兩個集合元素 set 的 table 元素的值必須指定,而且必須相同。set元素的兩個子元素:key 和 many-to-many 都必須指定 column 屬性,其中,key 和 many-to-many 分別指定本持久化類和關聯類在連接表中的外鍵列名,因此兩邊的 key 與 many-to-many 的column屬性交叉相同。也就是說,一邊的set元素的key的 cloumn值爲a,many-to-many 的 column 爲b;則另一邊的 set 元素的 key 的 column 值 b,many-to-many的 column 值爲 a。
- 對於雙向 n-n 關聯, 必須把其中一端的 inverse 設置爲 true。否則兩端都維護關聯關係可能會造成主鍵衝突.
實例代碼:
實體類Category:
public class Category {
private Integer categoryId;
private String categoryName;
private Set<Item> items = new HashSet<>();
//setxxx/getxxx方法
}
實體類Item:
public class Item {
private Integer id;
private String name;
private Set<Category> categories = new HashSet<>();
//getxxx/setxxx方法
}
對應的映射文件:
Category.hbm.xml:
<?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.zm.n2n.both">
<class name="Category" table="CATEGORIES">
<id name="categoryId" type="java.lang.Integer">
<column name="category_id"/>
<generator class="native"/>
</id>
<property name="categoryName" type="java.lang.String">
<column name="category_name"/>
</property>
<!--table:指定中間表-->
<set name="items" table="CATEGORIES_ITEMS">
<key>
<column name="C_ID"/>
</key>
<!--使用many-to-many來指定多對多的關聯關係,column指定Set集合中的持久化類在中間表的外鍵列的名稱-->
<many-to-many class="Item" column="I_ID"></many-to-many>
</set>
</class>
</hibernate-mapping>
Item.hbm.xml:
<?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.zm.n2n.both">
<class name="Item" table="ITEMS">
<id name="id" type="java.lang.Integer">
<column name="ID"/>
<generator class="native"/>
</id>
<property name="name" type="java.lang.String">
<column name="NAME"/>
</property>
<!--將items表作爲主動方-->
<set name="categories" table="CATEGORIES_ITEMS" inverse="true">
<key column="I_ID"></key>
<many-to-many class="Category" column="C_ID"></many-to-many>
</set>
</class>
</hibernate-mapping>
七、繼承關係映射
Hibernate 的繼承映射可以理解持久化類之間的繼承關係。例如:人和學生之間的關係。學生繼承了人,可以認爲學生是一個特殊的人,如果對人進行查詢,學生的實例也將被得到。
父類Person:
public class Person {
private Integer id;
private String name;
private Integer age;
//getxxx/setxxx方法
}
子類Student:
public class Student extends Person{
private String school;
//getxxx/setxxx方法
}
Hibernate支持三種繼承映射策略:
1、 subclass
將域模型中的每一個實體對象映射到一個獨立的表中,也就是說不用在關係數據模型中考慮域模型中的繼承關係和多態。
- 採用
subclass
的繼承映射可以實現對於繼承關係中父類和子類使用同一張表 - 因爲父類和子類的實例全部保存在同一個表中,因此需要在該表內增加一列,使用該列來區分每行記錄到低是哪個類的實例----這個列被稱爲辨別者列(discriminator)
- 在這種映射策略下,使用 subclass 來映射子類,使用 class 或 subclass 的 discriminator-value 屬性指定辨別者列的值
- 所有子類定義的字段都不能有非空約束。如果爲那些字段添加非空約束,那麼父類的實例在那些列其實並沒有值,這將引起數據庫完整性衝突,導致父類的實例無法保存到數據庫中
Person.hbm.xml映射文件:
<?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.zm.inherit.subclass">
<class name="Person" table="PERSON" discriminator-value="PERSON">
<id name="id" type="java.lang.Integer">
<column name="ID"/>
<generator class="native"/>
</id>
<!--配置辨別者列-->
<discriminator column="TYPE" type="java.lang.String"></discriminator>
<property name="name" type="java.lang.String">
<column name="NAME"/>
</property>
<property name="age" type="java.lang.Integer">
<column name="AGE"/>
</property>
<!--映射子類Student,使用subclass進行映射
discriminator-value:用於指定辨別者列的值
-->
<subclass name="Student" discriminator-value="STUDENT">
<property name="school" type="java.lang.String" column="SCHOOL"></property>
</subclass>
</class>
</hibernate-mapping>
(1)插入操作
/*插入操作:
* 1.對於子類對象只需把記錄插入到一張數據表中
* 2.辨別者列有Hibernate自動維護
* */
@Test
public void testSave(){
Person person = new Person();
person.setName("AA");
person.setAge(21);
session.save(person);
Student student = new Student();
student.setName("Stu_AA");
student.setAge(12);
student.setSchool("超星學院");
session.save(student);
}
所插入的數據在一張表中,以TYPE字段區分對象:
(2)查詢操作
/*查詢:
* 1.查詢父類記錄,只需要查詢一張數據表
* 2.對於子類記錄,也只需要查詢一張數據表(根據type)
* */
@Test
public void testGet(){
List<Person> persons = session.createQuery("from Person").list();
System.out.println(persons.size());
List<Student> students = session.createQuery("from Student").list();
System.out.println(students.size());
}
缺點:
① 使用了辨別者列
② 子類獨有的字段不能添加非空約束
③ 若繼承層次較深,則數據表的字段也會較多
2、joined-subclass
- 採用
joined-subclass
元素的繼承映射可以實現每個子類一張表。 - 採用這種映射策略時,父類實例保存在父類表中,子類實例由父類表和子類表共同存儲。因爲子類實例也是一個特殊的父類實例,因此必然也包含了父類實例的屬性。於是將子類和父類共有的屬性保存在父類表中,子類增加的屬性,則保存在子類表中。
- 在這種映射策略下,無須使用鑑別者列,但需要爲每個子類使用 key 元素映射共有主鍵。
- 子類增加的屬性可以添加非空約束。因爲子類的屬性和父類的屬性沒有保存在同一個表中
(1)插入操作
對於子類對象至少需要插入到兩張表中。
(2)查詢操作
① 查詢父類記錄,做一個左外連接
② 對於子類記錄,做一個內連接
優點:
① 不需要使用辨別者列
② 子類獨有的字段能添加非空約束
③ 沒有冗餘字段
3、 union-subclass
- 採用
union-subclass
元素可以實現將每一個實體對象映射到一個獨立的表中。 - 子類增加的屬性可以有非空約束 — 即父類實例的數據保存在父表中,而子類實例的數據保存在子類表中。
- 子類實例的數據僅保存在子類表中, 而在父類表中沒有任何記錄
- 在這種映射策略下,子類表的字段會比父類表的映射字段要多,因爲子類表的字段等於父類表的字段、加子類增加屬性的總和。
- 在這種映射策略下,既不需要使用鑑別者列,也無須使用 key 元素來映射共有主鍵。
- 使用
union-subclass
映射策略時不可使用 identity 的主鍵生成策略,因爲同一類繼承層次中所有實體類都需要使用同一個主鍵種子, 即多個持久化實體對應的記錄的主鍵應該是連續的。受此影響,也不該使用native
主鍵生成策略。因爲native
會根據數據庫來選擇使用identity
或sequence
.
(1)保存操作
對於子類對象只需把記錄插入到一張數據表中。
(2)查詢操作
① 查詢父類記錄,需把父表和子表彙總到一起再做查詢,性能較差
② 對於子類記錄,只需要查詢一張數據表
優點:
① 無需使用辨別者列
② 子類獨有的字段能添加非空約束
缺點:
① 存在冗餘字段
② 若更新父表的字段,則更新的效率較低
三種繼承映射方式的比較
推薦使用union-subclasss和joined-subclass