1. 程式人生 > >Hibernate學習筆記(四)—— 表與表的關係

Hibernate學習筆記(四)—— 表與表的關係

一、一對多|多對一

1.1 關係表達

1.1.1 表中的表達

  建表原則:在多的一方建立外來鍵指向一的一方的主鍵。

  

 

1.1.2 實體中的表達

    

  【客戶實體】

public class Customer {

    private Long cust_id;
    private String cust_name;
    private String cust_source;
    private String cust_industry;
    private String cust_level;
    private
String cust_linkman; private String cust_phone; private String cust_mobile; // 一個客戶有多個聯絡人:客戶中應該放有聯絡人的集合 private Set<LinkMan> linkMans = new HashSet<>(); get/set... }

  【聯絡人實體】

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; // Hibernate是一個ORM框架:在關係型資料庫中描述表與表之間的關係,使用的是外來鍵。開發語言使用的是Java,面向物件的 private
Customer customer;

  
   get/set...
}

1.1.3 配置檔案中的表達

  【客戶的對映】

<?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="cn.itcast.domain" >
    <class name="Customer" table="cst_customer" >
        <id name="cust_id"  >
            <generator class="native"></generator>
        </id>
        
        <property name="cust_name" column="cust_name" ></property>
        <property name="cust_source" column="cust_source" ></property>
        <property name="cust_industry" column="cust_industry" ></property>
        <property name="cust_level" column="cust_level" ></property>
        <property name="cust_linkman" column="cust_linkman" ></property>
        <property name="cust_phone" column="cust_phone" ></property>
        <property name="cust_mobile" column="cust_mobile" ></property>
        
        <!-- 配置關聯物件 -->
        <!-- name屬性:多的一方集合的屬性名稱 -->
        <set name="linkMans">
            <!-- column屬性:多的一方外來鍵的名稱 -->
            <key column="lkm_cust_id"></key>
            <!-- 多的一方類的全路徑,如果前面的package中設定了包名,這裡填類名即可 -->
            <one-to-many class="LinkMan"/>
        </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="cn.itcast.domain" >
    <class name="LinkMan" table="cst_linkman" >
        <id name="lkm_id"  >
            <generator class="native"></generator>
        </id>
        
        <property name="lkm_name" column="lkm_name" ></property>
        <property name="lkm_gender" column="lkm_gender" ></property>
        <property name="lkm_phone" column="lkm_phone" ></property>
        <property name="lkm_mobile" column="lkm_mobile" ></property>
        <property name="lkm_email" column="lkm_email" ></property>
        <property name="lkm_qq" column="lkm_qq" ></property>
        <property name="lkm_position" column="lkm_position" ></property>
        <property name="lkm_memo" column="lkm_memo" ></property>
        
        <!-- 配置關聯物件 -->
        <!-- name:一的一方的物件的名稱
            class:一的一方類的全路徑
            column:表中外來鍵的名稱
         -->
        <many-to-one name="customer" class="Customer" column="lkm_cust_id"/>
    </class>
</hibernate-mapping>

1.2 測試程式碼

  • 儲存
    // 儲存一個客戶和兩個聯絡人
        @Test
        public void testSave() throws Exception {
            // 獲得session
            Session session = HibernateUtils.openSession();
            // 開啟事務
            Transaction tx = session.beginTransaction();
            // 建立一個客戶
            Customer customer = new Customer();
            customer.setCust_name("李總");
            
            // 建立兩個聯絡人
            LinkMan linkMan1 = new LinkMan();
            linkMan1.setLkm_name("張祕書");
            LinkMan linkMan2 = new LinkMan();
            linkMan2.setLkm_name("王助理");
            
            // 表達一對多,客戶下有多個聯絡人
            customer.getLinkMans().add(linkMan1);
            customer.getLinkMans().add(linkMan2);
            
            // 表達多對一,聯絡人屬於哪個客戶
            linkMan1.setCustomer(customer);
            linkMan2.setCustomer(customer);
            
            session.save(customer);
            session.save(linkMan1);
            session.save(linkMan2);
            
            // 提交事務
            tx.commit();
            // 關閉資源
            session.close();
        }
  • 增加
    // 為客戶增加聯絡人
        @Test
        public void testAdd() throws Exception {
            // 獲得session
            Session session = HibernateUtils.openSession();
            // 開啟事務
            Transaction tx = session.beginTransaction();
            
            // 獲得要操作的客戶物件
            Customer customer = session.get(Customer.class, 1l);
            // 建立聯絡人
            LinkMan linkMan = new LinkMan();
            linkMan.setLkm_name("趙祕書");
            // 將聯絡人新增到客戶中
            customer.getLinkMans().add(linkMan);
            // 將客戶設定到聯絡人中
            linkMan.setCustomer(customer);
            // 執行儲存
            session.save(linkMan);
            
            tx.commit();
            session.close();
        }
  • 刪除
    // 為客戶刪除聯絡人
        @Test
        public void testDelete() throws Exception {
            // 獲得session
            Session session = HibernateUtils.openSession();
            // 開啟事務
            Transaction tx = session.beginTransaction();
            
            // 獲得要操作的客戶物件
            Customer customer = session.get(Customer.class, 1l);
            // 獲得要移除的聯絡人
            LinkMan linkMan = session.get(LinkMan.class, 2l);
            // 將聯絡人從客戶集合中移除
            customer.getLinkMans().remove(linkMan);
            linkMan.setCustomer(null);
            
            tx.commit();
            session.close();
        }

1.3 進階操作

  級聯操作是指當主控方執行儲存、更新或者刪除操作時,其關聯的物件(被控方)也執行相同的操作。在對映檔案中通過對cascade屬性的設定來控制是否對關聯物件採用級聯操作,級聯操作對各種關聯關係都是有效的。

1.3.1 級聯儲存或更新

  級聯是有方向性的,所謂的方向性指的是,在儲存一的一方級聯多的一方,和在儲存多的一方級聯一的一方。

【儲存客戶級聯聯絡人】

  首先要確定我們要儲存的主控方是哪一方,我們要儲存客戶,所以客戶是主控方,那麼需要在客戶的對映檔案中(Customer.hbm.xml)進行如下配置:

<!-- 配置關聯物件 -->
<!-- name屬性:多的一方集合的屬性名稱 -->
<set name="linkMans" cascade="save-update">
    <!-- column屬性:多的一方外來鍵的名稱 -->
    <key column="lkm_cust_id"></key>
    <!-- 多的一方類的全路徑,如果前面的package中設定了包名,這裡填類名即可 -->
    <one-to-many class="LinkMan"/>
</set>

   編寫測試程式碼:

// 級聯儲存:只儲存一邊的問題
    // 級聯是有方向性的,儲存客戶同時級聯客戶的聯絡人
    // 在Customer.hbm.xml的<set>標籤上配置cascade="save-update"
    @Test
    public void testCascade() throws Exception {
        Session session = HibernateUtils.openSession();
        Transaction tx = session.beginTransaction();
        // 建立一個客戶
        Customer customer = new Customer();
        customer.setCust_name("劉總");
        
        // 建立聯絡人
        LinkMan linkMan = new LinkMan();
        linkMan.setLkm_name("王祕書");
        
        // 建立關係
        customer.getLinkMans().add(linkMan);
        linkMan.setCustomer(customer);
        
        session.save(customer);
        
        tx.commit();
        session.close();
    }

 【儲存聯絡人級聯客戶】

   同樣我們需要確定主控方,現在我們的主控方是聯絡人。所以需要在聯絡人的對映檔案中(LinkMan.hbm.xml)進行配置:

<!-- 配置關聯物件 -->
        <!-- name:一的一方的物件的名稱
            class:一的一方類的全路徑
            column:表中外來鍵的名稱
         -->
        <many-to-one name="customer" cascade="save-update" class="Customer" column="lkm_cust_id"/>

   測試程式碼:

// 級聯儲存:只儲存一邊的問題
    // 級聯是有方向性的,儲存聯絡人同時級聯客戶
    // 在LinkMan.hbm.xml的<many-to-one>標籤上配置cascade="save-update"
    @Test
    public void testCascade2() throws Exception {
        Session session = HibernateUtils.openSession();
        Transaction tx = session.beginTransaction();
        
        // 建立客戶
        Customer customer = new Customer();
        customer.setCust_name("張總");
        
        // 建立聯絡人
        LinkMan linkMan = new LinkMan();
        linkMan.setLkm_name("柳助理");
        
        // 建立關係
        customer.getLinkMans().add(linkMan);
        linkMan.setCustomer(customer);
        
        session.save(linkMan);
        
        tx.commit();
        session.close();
    }

   到這我們已經可以看到級聯儲存或更新的效果了。那麼我們維護的時候都是雙向的關係維護。這種關係到底表述的是什麼含義呢?我們可以通過下面的測試來更進一步瞭解關係的維護(物件導航)的含義。

1.3.2 測試物件導航的問題

  我們所說的物件導航其實就是在維護雙方的關係。

customer.getLinkMans().add(linkMan);
linkMan.setCustomer(customer);

  這種關係有什麼用途呢?我們可以通過下面的測試來更進一步學習Hibernate。

  我們在客戶和聯絡人端都配置了cascade="save-update",然後進行如下的關係設定。會產生什麼樣的效果呢?

 1 // 測試物件導航和級聯操作
 2     @Test
 3     public void testCascade3() throws Exception {
 4         Session session = HibernateUtils.openSession();
 5         Transaction tx = session.beginTransaction();
 6 
 7         // 建立一個客戶
 8         Customer customer = new Customer();
 9         customer.setCust_name("趙總");
10 
11         // 建立三個聯絡人
12         LinkMan linkMan1 = new LinkMan();
13         linkMan1.setLkm_name("李祕書");
14         LinkMan linkMan2 = new LinkMan();
15         linkMan2.setLkm_name("王祕書");
16         LinkMan linkMan3 = new LinkMan();
17         linkMan3.setLkm_name("張助理");
18 
19         // 建立關係
20         linkMan1.setCustomer(customer);
21         customer.getLinkMans().add(linkMan2);
22         customer.getLinkMans().add(linkMan3);
23 
24         // 條件是雙方都設定了cascade="save-update"
25         session.save(linkMan1); // 資料庫中有幾條記錄?傳送了幾條insert語句? 4條
26         // session.save(customer); // 傳送了幾條insert語句? 3條
27         // session.save(linkMan2); // 傳送了幾條insert語句? 1條
28         
29         tx.commit();
30         session.close();
31     }

  我們在執行第25行程式碼時,問會執行幾條insert語句?其實發現會有4條insert語句,因為聯絡人1關聯了客戶,客戶又關聯了聯絡人2和聯絡人3,所以當儲存聯絡人1的時候,聯絡人1是可以進入到資料庫的,它關聯的客戶也是會進入到資料庫的(因為聯絡人一端配置了級聯),那麼客戶進入到資料庫以後,聯絡人2和聯絡人3也同樣會進入到資料庫(因為客戶一端配置了級聯),所以會有4條insert語句。

  我們在執行26行的時候,問會有幾條insert語句?這時我們儲存的客戶物件關聯了聯絡人2和聯絡人3,那麼客戶在進入資料庫的時候,聯絡人2和聯絡人3也會進入到資料庫,所以是3條insert語句。

  同理我們執行27行程式碼的時候,只會執行1條insert語句。因為聯絡人2會儲存到資料庫,但是聯絡人2沒有對客戶建立關係,所以客戶不會儲存到資料庫。

 1.3.3 Hibernate的級聯刪除

  我們之前學習過級聯儲存或更新,那麼再來看級聯刪除也就不難理解了,級聯刪除也是有方向性的,刪除客戶同時級聯刪除聯絡人,也可以刪除聯絡人同時級聯刪除客戶(這種需求很少)。

  原來JDBC中刪除客戶和聯絡人的時候,如果有外來鍵的關係是不可以刪除的,但是現在我們使用了Hibernate,其實Hibernate可以實現這樣的功能,但是不會刪除客戶的同時刪除聯絡人,預設情況下Hibernate會怎麼做呢?我們來看下面的測試:

// 刪除有級聯關係的物件:(預設情況:先將關聯物件的外來鍵置為null,再刪除)
    @Test
    public void demo5() throws Exception {
        Session session = HibernateUtils.openSession();
        Transaction tx = session.beginTransaction();
        
        Customer customer = session.get(Customer.class, 1l);
        session.delete(customer);
        
        tx.commit();
        session.close();
    }

  預設情況下如果客戶下面還有聯絡人,Hibernate會將聯絡人的外來鍵置為null,然後區刪除客戶。那麼其實有的時候我們需要刪除客戶的時候,同時將客戶關聯的聯絡人一起刪除。這個時候我們就需要使用Hibernate的級聯刪除操作了。

【刪除客戶的同時刪除客戶的聯絡人】

  確定刪除的主控方是客戶,所以需要在客戶端(Customer.hbm.xml)配置:

<!-- 配置關聯物件 -->
<!-- name屬性:多的一方集合的屬性名稱 -->
<set name="linkMans" cascade="delete">
    <!-- column屬性:多的一方外來鍵的名稱 -->
    <key column="lkm_cust_id"></key>
    <!-- 多的一方類的全路徑,如果前面的package中設定了包名,這裡填類名即可 -->
    <one-to-many class="LinkMan"/>
</set>

  如果還想有之前的級聯儲存或更新,同時還想有級聯刪除,那麼我們可以進行如下的配置:

<!-- 配置關聯物件 -->
<!-- name屬性:多的一方集合的屬性名稱 -->
<set name="linkMans" cascade="delete,save-update">
    <!-- column屬性:多的一方外來鍵的名稱 -->
    <key column="lkm_cust_id"></key>
    <!-- 多的一方類的全路徑,如果前面的package中設定了包名,這裡填類名即可 -->
    <one-to-many class="LinkMan"/>
</set>

  測試程式碼:

  // 級聯刪除:級聯刪除有方向性
    // 刪除客戶的同時刪除聯絡人
    // 在Customer.hbm.xml的<set>標籤上配置cascade="delete"
    @Test
    public void demo6() throws Exception {
        Session session = HibernateUtils.openSession();
        Transaction tx = session.beginTransaction();
        
        // 級聯刪除:必須是先查詢再刪除
        // 因為查詢到客戶,這個時候客戶的聯絡人集合就會有資料
        Customer customer = session.get(Customer.class, 7l);
        session.delete(customer);
        
        tx.commit();
        session.close();
    }

【刪除聯絡人的同時刪除客戶】

  這時候我們刪除的是聯絡人,那麼聯絡人就是主控方,需要在聯絡人端(LinkMan.hbm.xml)配置:

<!-- 配置關聯物件 -->
<!-- name:一的一方的物件的名稱
    class:一的一方類的全路徑
    column:表中外來鍵的名稱
 -->
<many-to-one name="customer" cascade="delete" class="Customer" column="lkm_cust_id"/>

  如果既要儲存或更新有級聯操作,又要有級聯刪除的功能,也可以如下配置:

<!-- 配置關聯物件 -->
<!-- name:一的一方的物件的名稱
    class:一的一方類的全路徑
    column:表中外來鍵的名稱
 -->
<many-to-one name="customer" cascade="delete,save-update" class="Customer" column="lkm_cust_id"/>

  測試程式碼:

  // 級聯刪除:級聯刪除有方向性
    // 刪除聯絡人的同時刪除客戶
    // 在LinkMan.hbm.xml中的<many-to-one>標籤上配置cascade="delete"
    @Test
    public void demo7() throws Exception {
        Session session = HibernateUtils.openSession();
        Transaction tx = session.beginTransaction();
        
        // 級聯刪除:必須是先查詢再刪除
        LinkMan linkMan = session.get(LinkMan.class, 4l);
        session.delete(linkMan);
        
        tx.commit();
        session.close();
    }

1.3.4 雙向關聯產生多餘的SQL語句

【客戶表】

  

【聯絡人表】

  

  有時候我們需要進行如下操作:將2號聯絡人關聯給2號客戶,也就是將2號李祕書這個聯絡人關聯給2號劉總這個客戶。

  編寫修改2號客戶關聯的聯絡人的程式碼:

  // 將2號聯絡人關聯的客戶改為2號客戶
    @Test
    public void demo8() throws Exception {
        Session session = HibernateUtils.openSession();
        Transaction tx = session.beginTransaction();
        
        Customer customer = session.get(Customer.class, 2l);
        LinkMan linkMan = session.get(LinkMan.class, 2l);
        
        linkMan.setCustomer(customer);
        customer.getLinkMans().add(linkMan);
        
        tx.commit();
        session.close();
    }

   執行上述程式碼,控制檯輸出如下內容:

    

  我們發現執行了兩次update語句,其實這兩個update都是修改了外來鍵的操作,那麼為什麼傳送兩次呢?我們來分析一下產生這個問題的原因:

  

  我們已經分析過了,因為雙向維護了關係,而且持久態物件可以自動更新資料庫,更新客戶的時候會修改一次外來鍵,更新聯絡人的時候同樣會修改一次外來鍵。這樣就會產生多餘的SQL。問題產生了,我們該如何解決呢?

  其實解決的辦法很簡單,只需要一方放棄外來鍵維護權即可。也就是說關係不是雙方維護的,只需要交給某一方去維護就可以了。通常我們都是交給多的一方去維護。為什麼呢?因為多的一方才是維護關係最好的地方。舉個例子,一個老師對應多個學生,一個學生對應一個老師,這時典型的一對多。一個老師如果要記住所有學生的名字很難,但如果讓每個學生記住老師的名字應該不難。其實就是這個道理。所有在一對多中,一的一方都會放棄外來鍵的維護權(關係的維護);

  這個時候如果想讓一的一方放棄外來鍵的維護權,只需要進行如下配置即可:

<!-- 配置關聯物件 -->
        <!-- name屬性:多的一方集合的屬性名稱 -->
        <set name="linkMans" cascade="save-update" inverse="true">
            <!-- column屬性:多的一方外來鍵的名稱 -->
            <key column="lkm_cust_id"></key>
            <!-- 多的一方類的全路徑,如果前面的package中設定了包名,這裡填類名即可 -->
            <one-to-many class="LinkMan"/>
        </set>

  inverse的預設值是false,代表不放棄外來鍵的維護權,配置值為true,代表放棄了外來鍵的維護權。這個時候再來執行之前的操作:

    

  這時候就不會出現上述問題了,不會產生多餘的SQL了(因為一的一方已經放棄了外來鍵的維護權)。

1.3.5 區分cascade和inverse

  // cascade和inverse
    // cascade強調的是操作一個物件的時候,是否操作其關聯的物件
    // inverse強調的是外來鍵的維護權
    @Test
    public void demo9() throws Exception {
        Session session = HibernateUtils.openSession();
        Transaction tx = session.beginTransaction();
        
        Customer customer = new Customer();
        customer.setCust_name("王總");
        
        LinkMan linkMan = new LinkMan();
        linkMan.setLkm_name("趙祕書");
        
        // 在Customer.hbm.xml的<set>標籤上配置cascade="save-update" inverse="true"
        customer.getLinkMans().add(linkMan);
        
        session.save(customer);
        
        tx.commit();
        session.close();
    }

   這時候我們會發現,如果在<set>標籤上配置cascade="save-update" inverse="true",那麼執行儲存客戶的操作時,會發現客戶和聯絡人都進入到資料庫了,但是沒有外來鍵。是因為配置了cascade="save-update"所以客戶關聯的聯絡人會進入到資料庫中,但是客戶一端放棄了外來鍵的維護權(inverse="true"),所以聯絡人插入到資料庫以後是沒有外來鍵的。

二、多對多

2.1 關係表達

2.1.1 表中的表達

  

  

2.1.2 物件中的表達

  

【使用者實體】

public class User {
    private Long user_id;
    private String user_code;
    private String user_name;
    private String user_password;
    private String user_state;
    
    // 使用者所屬的角色集合
    private Set<Role> roles = new HashSet<>();

  get/set... }

【角色實體】

public class Role {
    private Long role_id;
    private String role_name;
    private String role_memo;

    // 一個角色包含多個使用者
    private Set<User> users = new HashSet<>();

  get/set... }

 2.1.3 配置檔案中的表達

【使用者的對映】User.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="cn.itcast.domain" >
    <class name="User" table="sys_user" >
        <id name="user_id"  >
            <generator class="native"></generator>
        </id>
        
        <property name="user_code"/>
        <property name="user_name"/>
        <property name="user_password"/>
        <property name="user_state"/>
        
        <!-- 多對多關係表達 -->
        <!-- name:集合屬性名
            table:配置中間表名
         -->
        <set name="roles" table="sys_user_role">
            <!-- column:外來鍵,別人引用"我"的外來鍵列名 -->
            <key column="user_id"></key>
            <!-- class:我與哪個類是多對多關係
                column:外來鍵.我引用別人的外來鍵列名
             -->
            <many-to-many class="Role" column="role_id"></many-to-many>
        </set>
    </class>
</hibernate-mapping>

【角色的對映】Role.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="cn.itcast.domain" >
    <class name="Role" table="sys_role" >
        <id name="role_id"  >
            <generator class="native"></generator>
        </id>
        
        <property name="role_name"/>
        <property name="role_memo"/>
        
        <!-- 多對多關係表達 -->
        <!-- name:集合屬性名
            table:配置中間表名
         -->
        <set name="users" table="sys_user_role">
            <!-- column:外來鍵,別人引用"我"的外來鍵列名 -->
            <key column="role_id"></key>
            <!-- class:我與哪個類是多對多關係
                column:外來鍵.我引用別人的外來鍵列名
             -->
            <many-to-many class="User" column="user_id"></many-to-many>
        </set>
    </class>
</hibernate-mapping>

  在hibernate.cfg.xml中加入對映檔案

<mapping resource="cn/itcast/domain/User.hbm.xml" />
<mapping resource="cn/itcast/domain/Role.hbm.xml" />

2.2 測試方法

  •  儲存
      // 儲存使用者及角色
        @Test
        public void fun1() throws Exception {
            Session session = HibernateUtils.openSession();
            Transaction tx = session.beginTransaction();
            
            // 建立兩個User
            User user1 = new User();
            user1.setUser_name("張三");
            
            User user2 = new User();
            user2.setUser_name("李四");
            
            // 建立兩個Role
            Role role1 = new Role();
            role1.setRole_name("保潔");
            
            Role role2 = new Role();
            role2.setRole_name("保安");
            
            // 使用者表達關係
            user1.getRoles().add(role1);
            user1.getRoles().add(role2);
            
            user2.getRoles().add(role2);
            
            // 角色表達關係
            role1.getUsers().add(user1);
            role2.getUsers().add(user1);
            role2.getUsers().add(user2);
            
            // 呼叫save方法一次儲存
            session.save(user1);
            session.save(user2);
            session.save(role1);
            session.save(role2);
            
            tx.commit();
            session.close();
        }

     執行上述程式碼時,我們發現報錯了:

  

【錯誤原因分析】

   對映檔案中有一個inverse屬性,不配置時預設為false,即要維護關係。這裡兩方關係都設定了{user1.getRoles().add(role1);role1.getUsers().add(user1);},從配置上講,這兩方都沒放棄維護。多對多維護關係的手段是向中間表(sys_user_role)插入記錄,如果要表達1號使用者的角色是1號,那麼中間表就會插入1-1記錄。現在的情況是兩方都預設維護關係,role在維護關係的時候,表達1-1,會在中間表插入1-1記錄;user在維護關係的時候,也要往中間表插入資料,這時候user又要插入1-1記錄。中間表往往是這兩個id共同作為主鍵的,這兩個1-1就會出現主鍵重複。

  之前的一對多兩方都維護關係,卻沒發生異常,是因為它們維護關係,就是修改了外來鍵欄位,不存在向中間表插入記錄,所以修改兩次是沒有問題的,至少不會報錯,雖然效率會低點。而多對多維護關係是向中間表插入記錄,兩方都維護的時候,就會插入兩條一樣的記錄,就會產生主鍵重複。

  

【解決方案】

  方法一:對映檔案不改,從程式碼上來講,只要一方不表達關係即可,這樣在儲存的時候,因為程式碼上並沒有表達關係的邏輯,就不會給你操作了。這裡我們可以讓role的一方不表達關係,在程式碼上把相關程式碼註釋,此時方法就可執行成功

    

 

   方法二:程式碼不改,在一方的對映檔案中修改inverse為true,讓它放棄維護關係。這裡修改Role.hbm.xml

    

【結論】

   在多對多的儲存操作中,如果進行了雙向維護關係,就必須有一方放棄外來鍵維護權。一般由被動方放棄,使用者主動選擇角色,角色是被選擇的,所以一般角色要放棄外來鍵維護權。但如果只進行單向維護關係,那麼就不需要放棄外來鍵維護權了。

2.3 進階操作

2.3.1 級聯儲存或更新

【儲存使用者級聯角色】

  儲存的主控方是使用者,需要在使用者一端(User.hbm,xml)配置

  

  測試程式碼:

// 儲存使用者級聯角色
    @Test
    public void fun2() throws Exception {
        Session session = HibernateUtils.openSession();
        Transaction tx = session.beginTransaction();

        // 建立兩個User
        User user1 = new User();
        user1.setUser_name("劉備");

        User user2 = new User();
        user2.setUser_name("關羽");
        
        // 建立三個角色
        Role role1 = new Role();
        role1.setRole_name("人事管理");
        Role role2 = new Role();
        role2.setRole_name("行政管理");
        Role role3 = new Role();
        role3.setRole_name("財務管理");
        
        // 建立關係:如果建立了雙向的關係,一定要有一方放棄外來鍵維護權
        user1.getRoles().add(role1);
        user1.getRoles().add(role2);
        
        user2.getRoles().add(role3);
        user2.getRoles().add(role2);
        
        role1.getUsers().add(user1);
        role2.getUsers().add(user1);
        role2.getUsers().add(user2);
        role3.getUsers().add(user2);
        
        session.save(user1);
        session.save(user2);
        /*session.save(role1);
        session.save(role2);
        session.save(role3);*/
        
        tx.commit();
    }

【儲存角色級聯使用者】

  儲存的主控方是角色,需要在角色一端配置:

  

  測試程式碼:

// 儲存角色級聯使用者
    @Test
    public void fun2() throws Exception {
        Session session = HibernateUtils.openSession();
        Transaction tx = session.beginTransaction();

        // 建立兩個User
        User user1 = new User();
        user1.setUser_name("劉備");

        User user2 = new User();
        user2.setUser_name("關羽");
        
        // 建立三個角色
        Role role1 = new Role();
        role1.setRole_name("人事管理");
        Role role2 = new Role();
        role2.setRole_name("行政管理");
        Role role3 = new Role();
        role3.setRole_name("財務管理");
        
        // 建立關係:如果建立了雙向的關係,一定要有一方放棄外來鍵維護權
        user1.getRoles().add(role1);
        user1.getRoles().add(role2);
        
        user2.getRoles().add(role3);
        user2.getRoles().add(role2);
        
        role1.getUsers().add(user1);
        role2.getUsers().add(user1);
        role2.getUsers().add(user2);
        role3.getUsers().add(user2);
        
        /*session.save(user1);
        session.save(user2);*/
        session.save(role1);
        session.save(role2);
        session.save(role3);
        
        tx.commit();
    }

2.3.2 級聯刪除(瞭解)

  在多對多中級聯刪除是不會使用的,因為我們不會有這類的需求,比如刪除使用者,將使用者關聯的角色一起刪除,或者刪除角色的時候將使用者刪除掉,這時不合理的。但是級聯刪除的功能Hibernate已經提供了此功能,所以我們只需要瞭解即可。

【刪除使用者級聯角色】

   主控方是使用者,所以需要在使用者端配置

  

  測試程式碼:

// 級聯刪除:刪除使用者,級聯刪除角色
    @Test
    public void fun3() throws Exception {
        Session session = HibernateUtils.openSession();
        Transaction tx = session.beginTransaction();
        
        User user = session.get(User.class, 1l);
        session.delete(user);
        
        tx.commit();
        session.close();
    }

2.3.3 多對多的其他操作

  • 刪除某個使用者的角色
    // 將1號使用者的1號角色去掉
        @Test
        public void fun4() throws Exception {
            Session session = HibernateUtils.openSession();
            Transaction tx = session.beginTransaction();
            
            // 查詢1號使用者
            User user = session.get(User.class, 1l);
            // 查詢1號角色
            Role role = session.get(Role.class, 1l);
            
            // 操作集合 相當於操作資料庫
            user.getRoles().remove(role);
            
            tx.commit();
        }
  • 將某個使用者的角色改選
    // 將1號使用者的2號角色去掉,改為3號角色
        @Test
        public void fun5() throws Exception {
            Session session = HibernateUtils.openSession();
            Transaction tx = session.beginTransaction();
            
            // 查詢1號使用者
            User user = session.get(User.class, 1l);
            // 查詢2號角色
            Role role1 = session.get(Role.class, 2l);
            // 查詢3號角色
            Role role2 = session.get(Role.class, 3l);
            
            user.getRoles().remove(role1);
            user.getRoles().add(role2);
            
            tx.commit();
        }
  • 給某個使用者新增新角色
    // 給1號使用者新增2號角色
        @Test
        public void fun6() throws Exception {
            Session session = HibernateUtils.openSession();
            Transaction tx = session.beginTransaction();
            
            // 查詢1號使用者
            User user = session.get(User.class, 1l);
            // 查詢2號角色
            Role role = session.get(Role.class, 2l);
            
            user.getRoles().add(role);
            
            tx.commit();
        }