Hibernate 關聯關系映射

本文包括:

1、一對多結構的準備

2、雙向關聯與單向關聯

3、級聯保存

4、級聯刪除

5、cascade 屬性——級聯

6、inverse 屬性——放棄外鍵的維護

7、多對多結構的準備

8、多對多結構的級聯

1、一對多結構的準備

  • 需求分析:假設客戶和聯系人是一對多的關系,所以要在有客戶的情況下,要完成聯系人的添加保存操作。

  • 回想數據庫相關的知識,在建表的時候要注意客戶與聯系人的關系,一個客戶可以有多個聯系人,而聯系人必須依賴客戶而存在,所以客戶是“一”方,聯系人是“多”方。相應地,在數據庫中的“聯系人”表中,應增加一個字段,并且設為“客戶”表的外鍵。

    • 客戶表:

        CREATE TABLE `cst_customer` (
          `cust_id` bigint(32) NOT NULL AUTO_INCREMENT COMMENT '客戶編號(主鍵)',
          `cust_name` varchar(32) NOT NULL COMMENT '客戶名稱(公司名稱)',
          `cust_user_id` bigint(32) DEFAULT NULL COMMENT '負責人id',
          `cust_create_id` bigint(32) DEFAULT NULL COMMENT '創建人id',
          `cust_source` varchar(32) DEFAULT NULL COMMENT '客戶信息來源',
          `cust_industry` varchar(32) DEFAULT NULL COMMENT '客戶所屬行業',
          `cust_level` varchar(32) DEFAULT NULL COMMENT '客戶級別',
          `cust_linkman` varchar(64) DEFAULT NULL COMMENT '聯系人',
          `cust_phone` varchar(64) DEFAULT NULL COMMENT '固定電話',
          `cust_mobile` varchar(16) DEFAULT NULL COMMENT '移動電話',
          PRIMARY KEY (`cust_id`)
        ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
      
    • 聯系人表:

        CREATE TABLE `cst_linkman` (
          `lkm_id` bigint(32) NOT NULL AUTO_INCREMENT COMMENT '聯系人編號(主鍵)',
          `lkm_name` varchar(16) DEFAULT NULL COMMENT '聯系人姓名',
          `lkm_cust_id` bigint(32) NOT NULL COMMENT '客戶id',
          `lkm_gender` char(1) DEFAULT NULL COMMENT '聯系人性別',
          `lkm_phone` varchar(16) DEFAULT NULL COMMENT '聯系人辦公電話',
          `lkm_mobile` varchar(16) DEFAULT NULL COMMENT '聯系人手機',
          `lkm_email` varchar(64) DEFAULT NULL COMMENT '聯系人郵箱',
          `lkm_qq` varchar(16) DEFAULT NULL COMMENT '聯系人qq',
          `lkm_position` varchar(16) DEFAULT NULL COMMENT '聯系人職位',
          `lkm_memo` varchar(512) DEFAULT NULL COMMENT '聯系人備注',
          PRIMARY KEY (`lkm_id`),
          KEY `FK_cst_linkman_lkm_cust_id` (`lkm_cust_id`),
          CONSTRAINT `FK_cst_linkman_lkm_cust_id` FOREIGN KEY (`lkm_cust_id`) REFERENCES `cst_customer` (`cust_id`) ON DELETE NO ACTION ON UPDATE NO ACTION
        ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
      
  • JavaBean 代碼(省略 get 和 set 方法):

    • 客戶的 JavaBean 如下

        public class Customer {
            private Long cust_id;
            private String cust_name;
            private Long cust_user_id;
            private Long cust_create_id;
            private String cust_source;
            private String cust_industry;
            private String cust_level;
            private String cust_linkman;
            private String cust_phone;
            private String cust_mobile;
            // 注意:在“一”方的 JavaBean 中要添加 set 集合!
            private Set<Linkman> linkmans = new HashSet<Linkman>();
      
        }
      
    • 聯系人的 JavaBean 如下:

        public class Linkman {
            private Long lkm_id;
            private String lkm_name;
            private String lkm_gender;
            private String lkm_phone;
            private String lkm_mobile;
            private String lkm_email;
            private String lkm_qq;
            private String lkm_position;
            private String lkm_memo;
            // 注意:這里不寫外鍵字段而寫 Customer 對象,方便訪問客戶對象,而且是 Hibernate 要求的!
            private Customer customer; // 注意:千萬不要 new!
            
        }
      
  • 編寫客戶和聯系人的映射配置文件(注意一對多的配置編寫)

    • 客戶的映射配置文件如下:

        <class name="com.itheima.domain.Customer" table="cst_customer">
            <id name="cust_id" column="cust_id">
                <generator class="native"/>
            </id>
            <property name="cust_name" column="cust_name"/>
            <property name="cust_user_id" column="cust_user_id"/>
            <property name="cust_create_id" column="cust_create_id"/>
            <property name="cust_source" column="cust_source"/>
            <property name="cust_industry" column="cust_industry"/>
            <property name="cust_level" column="cust_level"/>
            <property name="cust_linkman" column="cust_linkman"/>
            <property name="cust_phone" column="cust_phone"/>
            <property name="cust_mobile" column="cust_mobile"/>
      
            <!-- 配置一方 -->
            <!-- set標簽name屬性:表示集合的名稱 -->            
            <set name="linkmans">
                <key column="lkm_cust_id"/>
                <one-to-many class="com.itheima.domain.Linkman"/>
            </set>
        </class>
      
    • 聯系人的映射配置文件如下:

        <class name="com.itheima.domain.Linkman" table="cst_linkman">
            <id name="lkm_id" column="lkm_id">
                <generator class="native"/>
            </id>
            <property name="lkm_name" column="lkm_name"/>
            <property name="lkm_gender" column="lkm_gender"/>
            <property name="lkm_phone" column="lkm_phone"/>
            <property name="lkm_mobile" column="lkm_mobile"/>
            <property name="lkm_email" column="lkm_email"/>
            <property name="lkm_qq" column="lkm_qq"/>
            <property name="lkm_position" column="lkm_position"/>
            <property name="lkm_memo" column="lkm_memo"/>
      
            <!-- 先配置多方 
                name    當前JavaBean中的屬性
                class   屬性的全路徑
                column  外鍵的字段
            -->
            <many-to-one name="customer" class="com.itheima.domain.Customer" column="lkm_cust_id"/>
        </class>
      

2、雙向關聯與單向關聯

  • 雙向關聯:

    • 假設現在要保存一個客戶,而且需要同時保存聯系人字段,假設代碼如下:

        Customer c1 = new Customer();
        c1.setCust_name("美美");
        
        Linkman l1 = new Linkman();
        l1.setLkm_name("熊大");
        Linkman l2 = new Linkman();
        l2.setLkm_name("熊二");
        
        // 演示雙向關聯
        c1.getLinkmans().add(l1);
        c1.getLinkmans().add(l2);
        
        l1.setCustomer(c1);
        l2.setCustomer(c1);
        
        session.save(l1);
        session.save(l2);
        session.save(c1);
      
    • 執行代碼,程序正常運行,查詢數據庫,客戶表新增一條記錄,聯系人表新增兩條記錄。

    • 注意:c1 一定要最后保存,否則會報錯,因為瞬時態 c1 持有瞬時態 l1、l2,必須要 l1、l2 轉變為持久態,c1 才能轉變為持久態。

      關于持久化類的三種狀態(瞬時態、持久態、托管態)及其之間的相互轉化參考:http://www.lxweimin.com/p/1a6ca1993b16

    • 由此可見,雙向關聯是最淺顯易懂的,但代碼也最復雜。

  • 單向關聯:

    • 如果只想在客戶表新增記錄,而聯系人表不變,這就叫單向關聯。

        Customer c1 = new Customer();
        c1.setCust_name("美美");
        
        // 創建2個聯系人
        Linkman l1 = new Linkman();
        l1.setLkm_name("熊大");
        Linkman l2 = new Linkman();
        l2.setLkm_name("熊二");
        
        // 單向關聯
        c1.getLinkmans().add(l1);
        c1.getLinkmans().add(l2);
        
        // 保存數據
        session.save(c1);
      
    • 如果不配置級聯保存(見下節),則程序出現異常,異常第一行如下:

        org.hibernate.TransientObjectException: 
            object references an unsaved transient instance - 
                save the transient instance before flushing: 
                    com.itheima.domain.Linkman
      
    • 簡單分析:在調用 save 方法時,c1 要由瞬時態轉變為持久態,而 l1、l2仍然是瞬時態,而 c1 持有 l1、l2,所以程序出錯。

3、級聯保存

  • 級聯保存

    1. 測試:如果現在代碼只插入其中的一方的數據(單向關聯)

      • 如果只保存其中的一方的數據,那么程序會拋出異常。

      • 如果想完成只保存一方的數據,并且把相關聯的數據都保存到數據庫中,那么需要配置級聯!!

      • 級聯保存是方向性

    2. 級聯保存效果

      • 級聯保存:保存一方同時可以把關聯的對象也保存到數據庫中!!

      • 使用 cascade="save-update"

    3. 客戶級聯聯系人

      • 如果想在保存客戶時,同時也保存聯系人,在客戶的配置文件中這樣編寫:

          <set name="linkmans" cascade="save-update">
              <!-- 需要出現子標簽 -->
              <!-- 外鍵的字段 -->
              <key column="lkm_cust_id"/>
              <one-to-many class="com.itheima.domain.Linkman"/>
          </set>
        
      • 再執行如下代碼,正常運行,客戶表新增一條記錄,聯系人表新增兩條記錄。

          Customer c1 = new Customer();
          c1.setCust_name("美美");
          
          // 創建2個聯系人
          Linkman l1 = new Linkman();
          l1.setLkm_name("熊大");
          Linkman l2 = new Linkman();
          l2.setLkm_name("熊二");
          
          // 單向關聯
          c1.getLinkmans().add(l1);
          c1.getLinkmans().add(l2);
          
          // 保存數據
          session.save(c1);
        
    • 聯系人級聯客戶

      • 同樣地,如果想在保存聯系人時,同時也保存客戶,在聯系人的配置文件中這樣編寫:

          <!-- 先配置多方 
                      name    當前JavaBean中的屬性
                      class   屬性的全路徑
                      column  外鍵的字段
                  -->
          <many-to-one name="customer" class="com.itheima.domain.Customer" column="lkm_cust_id" cascade="save-update"/>
        
      • 再執行如下代碼,正常運行,客戶表新增一條記錄,聯系人表新增兩條記錄。

          Customer c1 = new Customer();
          c1.setCust_name("美美");
          
          // 創建2個聯系人
          Linkman l1 = new Linkman();
          l1.setLkm_name("熊大");
          Linkman l2 = new Linkman();
          l2.setLkm_name("熊二");
          
          // 使用聯系人關聯客戶
          l1.setCustomer(c1);
          l2.setCustomer(c1);
          
          // 保存
          session.save(l1);
          session.save(l2);
        
    • 客戶與聯系人互相級聯保存

      • 如果在兩個配置文件中都配置 cascade="save-update" ,那么兩者互相級聯。

      • 測試如下代碼,發現程序正常運行,客戶表新增一條記錄,聯系人表新增兩條記錄。

          Customer c1 = new Customer();
          c1.setCust_name("美美");
          
          // 創建2個聯系人
          Linkman l1 = new Linkman();
          l1.setLkm_name("熊大");
          Linkman l2 = new Linkman();
          l2.setLkm_name("熊二");
          
          l1.setCustomer(c1);
          c1.getLinkmans().add(l2);
          session.save(l1);
        

4、級聯刪除

  • 級聯刪除
    1. 假設現在要刪除某個客戶,在含有外鍵約束的情況下,是不會成功的。

       delete from customers where cid = 1;
      

      error:

       [Err] 1451 - Cannot delete or update a parent row: a foreign key constraint fails (`hibernate_day03`.`cst_linkman`, CONSTRAINT `FK_cst_linkman_lkm_cust_id` FOREIGN KEY (`lkm_cust_id`) REFERENCES `cst_customer` (`cust_id`) ON DELETE NO ACTION ON UPDATE NO ACTION)
      
    2. 如果使用 Hibernate 直接刪除客戶的時候,測試發現是可以刪除的,這是為什么呢?我們直接看控制臺的輸出:

       Hibernate: 
           select
               customer0_.cust_id as cust_id1_0_0_,
               customer0_.cust_name as cust_nam2_0_0_,
               customer0_.cust_user_id as cust_use3_0_0_,
               customer0_.cust_create_id as cust_cre4_0_0_,
               customer0_.cust_source as cust_sou5_0_0_,
               customer0_.cust_industry as cust_ind6_0_0_,
               customer0_.cust_level as cust_lev7_0_0_,
               customer0_.cust_linkman as cust_lin8_0_0_,
               customer0_.cust_phone as cust_pho9_0_0_,
               customer0_.cust_mobile as cust_mo10_0_0_ 
           from
               cst_customer customer0_ 
           where
               customer0_.cust_id=?
       Hibernate: 
           update
               cst_linkman 
           set
               lkm_cust_id=null 
           where
               lkm_cust_id=?
       Hibernate: 
           delete 
           from
               cst_customer 
           where
               cust_id=?
      

      注意:Hibernate 自動執行了3條 SQL 語句,其中第2條尤其值得一看,它把聯系人表中與該客戶有關的外鍵的值設為 null,也就是說:這個客戶沒有聯系人了,自然也就可以刪除了。

    3. 上述的刪除是普通的刪除,那么也可以使用級聯刪除,級聯刪除有個好處:當我們把這個客戶刪除了,那這個客戶的聯系人自然也就沒什么意義了。

    4. 注意:級聯刪除也是有方向性的!!

      • 假設在刪除客戶時,要刪除該客戶所對應的聯系人,則應該在客戶的配置文件中這樣配置:

          <set name="linkmans" cascade="save-update,delete">
              <!-- 需要出現子標簽 -->
              <!-- 外鍵的字段 -->
              <key column="lkm_cust_id"/>
              <one-to-many class="com.itheima.domain.Linkman"/>
          </set>
        
      • 相反地,若要在刪除聯系人時,同時刪除對應的客戶,則在聯系人的配置文件中這樣配置:

          <many-to-one name="customer" class="com.itheima.domain.Customer" column="lkm_cust_id" cascade="save-update,delete"/>
        
      • 也可以像級聯保存那樣,互相級聯,那樣就可以實現:刪除一個聯系人 - 刪除對應的客戶 - 刪除該客戶所對應的所有聯系人

      • 注意:級聯刪除要慎重!在實際情況中,很多時候是假刪除,即添加一個字段,用該字段來表示這條記錄是否刪除了,多用 update ,而不會真正的 delete !

5、cascade 屬性——級聯

  • 之前的級聯保存、級聯刪除,都是在配置文件中配置這樣一個屬性 cascade,接下來探討一下這個屬性的取值。

  • 級聯的取值

    • 需要掌握的取值如下:
      • none -- 不使用級聯
      • save-update -- 級聯保存或更新
      • delete -- 級聯刪除
      • delete-orphan -- 孤兒刪除.(注意:只能應用在一對多關系)
      • all -- 除了delete-orphan的所有情況.(包含save-update,delete)
      • all-delete-orphan -- 包含了delete-orphan的所有情況.(包含save-update,delete,delete-orphan)
  • 孤兒刪除

    • 孤兒刪除(孤子刪除),只有在一對多的環境下才有孤兒刪除

      • 在一對多的關系中,可以將“一”的一方認為是父方,將“多”的一方認為是子方。孤兒刪除:當解除了父子關系的時候,將子方記錄就直接刪除。
      • 若想在解除關系時,把聯系人的記錄從數據庫刪除,則應該在客戶的配置文件中中這樣編寫:

          <!-- 配置一方 -->
          <!--
              set標簽name屬性:表示集合的名稱
          -->
          <set name="linkmans" cascade="delete-orphan">
              <!-- 需要出現子標簽 -->
              <!-- 外鍵的字段 -->
              <key column="lkm_cust_id"/>
              <one-to-many class="com.itheima.domain.Linkman"/>
          </set>
        
      • 解除父子關系的代碼:

          // 先獲取到客戶
          Customer c1 = session.get(Customer.class, 1L);
          Linkman l1 = session.get(Linkman.class, 1L);
          // 解除
          c1.getLinkmans().remove(l1);
        
      • 最后查詢數據庫,發現聯系人表中 id 為1的記錄被刪除了。

6、inverse 屬性——放棄外鍵的維護

  1. 在之前的例子中,默認雙方都維護外鍵,會產生多余的SQL語句。

    • 現象:想修改客戶和聯系人的關系,進行雙向關聯,雙方都會維護外鍵,會產生多余的SQL語句。

    • 原因:session 的一級緩存中的快照機制,會讓雙方都更新數據庫,產生了多余的 SQL 語句。

  2. 如果不想產生多余的SQL語句,那么需要一方來放棄外鍵的維護,通常是“一”方來放棄外鍵的維護,于是在“一”方的配置文件中這樣編寫:

    • 在<set>標簽上配置一個 inverse="true" :true:放棄;false:不放棄;默認值是 false

        <inverse="true">
      

    注意:在一對多的情況下,可以不放棄外鍵的維護,但是下節開始的多對多表結構必須有一方放棄外鍵的維護。

  3. cascade 和 inverse 的區別

    • cascade 用來級聯操作(保存、修改和刪除)

    • inverse 用來維護外鍵

      舉例:若 Customer 同時配置 cascade="save-update"<inverse="true">,當保存一個 Customer 對象時,數據庫也會同時保存 Linkman 對象(級聯保存的功勞),但是該 Linkman 對象的外鍵是空的。

    注意:在實際情況中,大部分情況是:“一”方配置 <inverse="true"> ,“多”方配置 cascade="save-update"

7、多對多結構的準備

  • 需求分析:用戶與角色是多對多的關系,一個用戶可能有多種角色,而某種角色可能被多個用戶共享。

  • 數據庫知識回顧:在之前的 Java web 學習中,對于多對多的情況,要創建一個中間表,這個中間表通過外鍵聯系了兩張表,而在 Hibernate 中,不需要手動創建中間表,只需要按照 Hibernate 的規范編寫 JavaBean 和其對應的配置文件即可。

  • JavaBean 代碼(省略 set 和 get 方法):

    • 用戶:

        public class User {
            
            private Long uid;
            private String username;
            private String password;
            
            // 編寫都是集合
            private Set<Role> roles = new HashSet<Role>();
        
        }
      
    • 角色:

        public class Role {
            
            private Long rid;
            private String rname;
            
            private Set<User> users = new HashSet<User>();
        
        }
      
  • 用戶與角色的配置文件編寫:

    • 用戶的映射配置文件如下

        <class name="com.itheima.domain.User" table="sys_user">
            <id name="user_id" column="user_id">
                <generator class="native"/>
            </id>
            <property name="user_code" column="user_code"/>
            <property name="user_name" column="user_name"/>
            <property name="user_password" column="user_password"/>
            <property name="user_state" column="user_state"/>
            
            <set name="roles" table="sys_user_role">
                <key column="user_id"/>
                <many-to-many class="com.itheima.domain.Role" column="role_id"/>
            </set>
        </class>
      

      中間表的名字就叫做 sys_user_role

    • 角色的映射配置文件如下

        <class name="com.itheima.domain.Role" table="sys_role">
            <id name="role_id" column="role_id">
                <generator class="native"/>
            </id>
            <property name="role_name" column="role_name"/>
            <property name="role_memo" column="role_memo"/>
            
            <set name="users" table="sys_user_role">
                <key column="rid"/>
                <many-to-many class="com.itheima.domain.User" column="uid"/>
            </set>
        </class>
      

      中間表的名字就叫做 sys_user_role

    • 多對多進行雙向關聯的時候:必須有一方去放棄外鍵維護權,如果不放棄中間表會被更新兩次,不允許!

      故,可選擇角色的映射配置文件,改為:

        <!-- 多對多必須要有一方放棄外鍵的維護的 -->
        <set name="users" table="sys_user_role" inverse="true">
            <key column="rid"/>
            <many-to-many class="com.itheima.domain.User" column="uid"/>
        </set>
      

8、多對多結構的級聯

  • 關于級聯的各種取值、概念在前文已有,在此不再贅述。

  • 級聯保存:save-update ,大部分情況下都是這個取值。

    以下是一個示例,角色放棄外鍵的維護,用戶級聯角色:

      // 模擬多對多,雙向的關聯
      User u1 = new User();
      u1.setUsername("張三");
      User u2 = new User();
      u2.setUsername("趙四");
      
      // 創建角色
      Role r1 = new Role();
      r1.setRname("經理");
      Role r2 = new Role();
      r2.setRname("演員");
      
      u1.getRoles().add(r1);
      u1.getRoles().add(r2);
      u2.getRoles().add(r1);
      
      // 保存數據
      session.save(u1);
      session.save(u2);
    
  • 級聯刪除:在多對多情況下,極少使用。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,973評論 19 139
  • hibernate(20170731) 1.導包:hibernate-distribution-3.5.6-Fin...
    瀟湘雨smile閱讀 561評論 0 0
  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,767評論 18 399
  • 本文包括: 1、CRM 項目的整體介紹 2、Hibernate 框架概述 3、Hibernate 快速入門 4、H...
    廖少少閱讀 3,485評論 9 66
  • 逗你的 你用這后半句 來取消之前的一切 我無從反抗 不能認真 你說 你喜歡我 我還在等著 等著你說這后半句 不敢放心
    一團無知的肉閱讀 148評論 0 0