08-hibernate中的幾種關係映射

一、映射組成關係

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-manymany-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 會根據數據庫來選擇使用 identitysequence.
    在這裏插入圖片描述
    在這裏插入圖片描述在這裏插入圖片描述

(1)保存操作

對於子類對象只需把記錄插入到一張數據表中。

(2)查詢操作

① 查詢父類記錄,需把父表和子表彙總到一起再做查詢,性能較差
② 對於子類記錄,只需要查詢一張數據表

優點:
① 無需使用辨別者列
② 子類獨有的字段能添加非空約束
缺點:
① 存在冗餘字段
② 若更新父表的字段,則更新的效率較低

三種繼承映射方式的比較

在這裏插入圖片描述
推薦使用union-subclasss和joined-subclass

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章