三、Hibernate_多表關(guān)系&級聯(lián)操作&外鍵維護(hù)

一、 一對多的ORM關(guān)系映射

ORM : Object Relational Mapping 。 對象 關(guān)系 映射。

  1. 首先完成Relational數(shù)據(jù)庫表的一對多的關(guān)系
  2. 完成Object實(shí)體對象的一對多關(guān)系
  3. 完成Mapping映射文件中一對多關(guān)系的映射配置

一對多的實(shí)例:客戶-聯(lián)系人

一個客戶(公司)---對應(yīng)---多個聯(lián)系人

步驟:

  1. 創(chuàng)建兩個數(shù)據(jù)庫表customer,linkman,在多的一方添加外鍵建立關(guān)系
  2. 創(chuàng)建兩個實(shí)體類Customer和Linkman,在實(shí)體類中分別建立與對方的關(guān)系
  3. 創(chuàng)建兩個實(shí)體類對應(yīng)的配置文件,分別配置完成一對多的關(guān)系映射

1.導(dǎo)入jar包(省略)

導(dǎo)入Hibernate所需要的一些jar包。

2. 創(chuàng)建數(shù)據(jù)庫表customer與linkman,并建立聯(lián)系。R

    創(chuàng)建customer表與linkman表,在多的一方linkman的表中添加外鍵,指向一的一方的主鍵
    
    create database hibernate_day03;
    
    use hibernate_day03;
    
    客戶表 :
    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 '負(fù)責(zé)人id',
      `cust_create_id` bigint(32) DEFAULT NULL COMMENT '創(chuàng)建人id',
      `cust_source` varchar(32) DEFAULT NULL COMMENT '客戶信息來源',
      `cust_industry` varchar(32) DEFAULT NULL COMMENT '客戶所屬行業(yè)',
      `cust_level` varchar(32) DEFAULT NULL COMMENT '客戶級別',
      `cust_linkman` varchar(64) DEFAULT NULL COMMENT '聯(lián)系人',
      `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;


    聯(lián)系人數(shù)據(jù)庫表 :
    CREATE TABLE `cst_linkman` (
      `lkm_id` bigint(32) NOT NULL AUTO_INCREMENT COMMENT '聯(lián)系人編號(主鍵)',
      `lkm_name` varchar(16) DEFAULT NULL COMMENT '聯(lián)系人姓名',
      `lkm_cust_id` bigint(32) NOT NULL COMMENT '客戶id',
      `lkm_gender` char(1) DEFAULT NULL COMMENT '聯(lián)系人性別',
      `lkm_phone` varchar(16) DEFAULT NULL COMMENT '聯(lián)系人辦公電話',
      `lkm_mobile` varchar(16) DEFAULT NULL COMMENT '聯(lián)系人手機(jī)',
      `lkm_email` varchar(64) DEFAULT NULL COMMENT '聯(lián)系人郵箱',
      `lkm_qq` varchar(16) DEFAULT NULL COMMENT '聯(lián)系人qq',
      `lkm_position` varchar(16) DEFAULT NULL COMMENT '聯(lián)系人職位',
      `lkm_memo` varchar(512) DEFAULT NULL COMMENT '聯(lián)系人備注',
      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
    )

3. 創(chuàng)建Customer與Linkman實(shí)體類,并建立聯(lián)系。O

分別在兩個實(shí)體類中添加與另一個實(shí)體類的聯(lián)系。

Customer的實(shí)體類:

img17.png

Linkman的實(shí)體類:

img18.png
4. 在對應(yīng)的映射文件中添加一對多的映射關(guān)系

Customer.hbm.xml映射文件:


    <!-- 集合,一對多關(guān)系,在映射文件中配置 -->
    <!-- 
        name屬性:集合屬性名
        column屬性: 外鍵列名(通過這個外鍵找到Linkman放入集合)
        class屬性: 與我關(guān)聯(lián)的對象完整類名(從哪里找)
     -->
    <set name="linkemans">
        <key column="lkm_cust_id"></key>
        <one-to-many class="com.itdream.domain.Linkeman"/>
    </set>

Linkman.hbm.xml映射文件:


    <!-- 多對一配置 -->
    <!-- 
        name屬性:引用的屬性名
        column屬性: 外鍵列名(通過外鍵找到這個引用)
        class屬性: 與我關(guān)聯(lián)的對象完整類名(從哪找)
     -->
    <many-to-one name="customer" column="lkm_cust_id" class="com.itdream.domain.Customer"></many-to-one>
5.在核心配置文件中加載映射文件

    <?xml version="1.0" encoding="UTF-8"?>
    
    <!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
        "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
    
    <!-- 配置Hibernate的核心配置文件 --> 
    <hibernate-configuration>
        <session-factory>
            <!-- 1.Hibernate連接數(shù)據(jù)庫的基本配置 -->
            <property name="hibernate.connection.driver_class">com.mysql.jdbc.Driver</property>
            <property name="hibernate.connection.url">jdbc:mysql://localhost:3306/hibernate_day03</property>
            <property name="hibernate.connection.username">root</property>
            <property name="hibernate.connection.password">root</property>
            
            <!-- 2.配置與當(dāng)前線程綁定的Session對象 -->
            <property name="hibernate.current_session_context_class">thread</property>
            
            <!-- 3.Hibernate的普通配置 -->
            <!-- Hibernate的方言配置,根據(jù)配置生成對應(yīng)SQL語句.這實(shí)現(xiàn)了Hibernate跨數(shù)據(jù)庫.更換數(shù)據(jù)庫只需要修改這里的配置即可-->
            <property name="hibernate.dialect">org.hibernate.dialect.MySQLDialect</property>
            
            <!-- 顯示sql語句 -->
            <property name="hibernate.show_sql">true</property>
            
            <!-- 格式化sql語句 -->
            <property name="hibernate.format_sql">true</property>
            
            <!-- Hibernate自動創(chuàng)建數(shù)據(jù)庫表的方式 hbm2ddl-->
            <property name="hibernate.hbm2ddl.auto">none</property>
            
            <!-- 設(shè)置隔離級別:4:Repeatable read可重復(fù)讀 -->
            <property name="hibernate.isolation">4</property>
            
            <!-- c3p0連接池配置-->
            <property name="hibernate.connection.provider_class">org.hibernate.c3p0.internal.C3P0ConnectionProvider</property>
            <!-- 最小連接 -->
            <property name="hibernate.c3p0.min_size">5</property>
            <!-- 最大連接數(shù) -->
            <property name="hibernate.c3p0.max_size">20</property>
            <!-- 連接超時時長 -->
            <property name="hibernate.c3p0.timeout">120</property>
            <!-- 每120秒檢查空閑連接 -->
            <property name="hibernate.c3p0.idle_test_period">120</property>
            <!-- 最大statments數(shù)量 -->
            <property name="hibernate.c3p0.max_statements">120</property>
            <!-- 連接用完后,每次增加的連接數(shù) -->
            <property name="hibernate.c3p0.acquire_increment">2</property>
            <!-- 每次都驗(yàn)證連接是否可用 -->
            <property name="hibernate.c3p0.validate">false</property>
            
            <!-- 4.加載映射配置 -->
            <mapping resource="com/itdream/domain/Customer.hbm.xml"/>
            <mapping resource="com/itdream/domain/Linkman.hbm.xml"/>
        </session-factory>
    </hibernate-configuration>

6.Hibernate:1對多關(guān)系測試

    /**
     * 測試數(shù)據(jù)庫表的1對多關(guān)系:
     */
    
    @Test
    //保存一個客戶與兩個聯(lián)系人
    public void test() {
        //獲取Session對象
        Session session = HibernateUtils.getCurrentSession();
        //開啟事務(wù)
        Transaction transaction = session.beginTransaction();
        //------------------------------------------
        
        //1> 創(chuàng)建一個客戶
        Customer customer = new Customer();
        customer.setCust_name("阿里巴巴");
        
        //2> 創(chuàng)建兩個聯(lián)系人
        Linkman linkeman = new Linkman();
        linkeman.setLkm_name("小馬");
        
        Linkman linkeman2 = new Linkman();
        linkeman2.setLkm_name("小云");
        
        //3> 表達(dá)一對多,客戶下有多個聯(lián)系人
        customer.getLinkemans().add(linkeman);
        customer.getLinkemans().add(linkeman2);
        
        //4> 表達(dá)多對一,聯(lián)系人屬于哪個客戶
        linkeman.setCustomer(customer);
        linkeman2.setCustomer(customer);
        
        //5> 保存.將瞬時態(tài)對象轉(zhuǎn)換成持久態(tài)對象
        session.save(customer);
        session.save(linkeman);
        session.save(linkeman2);
        
        //------------------------------------------
        //提交事務(wù)
        transaction.commit();
    }
    
    
    @Test
    //為客戶1添加一個聯(lián)系人
    public void test2() {
        //獲取Session對象
        Session session = HibernateUtils.getCurrentSession();
        //開啟事務(wù)
        Transaction transaction = session.beginTransaction();
        //------------------------------------------
        
        //1> 獲取客戶1
        Customer customer = session.get(Customer.class, 1L);
        
        //2> 創(chuàng)建待添加的聯(lián)系人
        Linkman linkeman = new Linkman();
        linkeman.setLkm_name("劉總");
        
        //3> 建立關(guān)系:表達(dá)一對多,客戶下有多個聯(lián)系人
        customer.getLinkemans().add(linkeman);
        
        //4> 建立關(guān)系:表達(dá)多對一,聯(lián)系人屬于哪個客戶
        linkeman.setCustomer(customer);
        
        //5> 保存.將瞬時態(tài)對象轉(zhuǎn)換成持久態(tài)對象
        //session.save(customer);(customer是持久態(tài)對象無需手動保存,會自動更新數(shù)據(jù)庫)
        session.save(linkeman);
        
        //------------------------------------------
        //提交事務(wù)
        transaction.commit();
    }
    
    
    @Test
    //為客戶1刪除一個聯(lián)系人劉總
    public void test3() {
        //獲取Session對象
        Session session = HibernateUtils.getCurrentSession();
        //開啟事務(wù)
        Transaction transaction = session.beginTransaction();
        //------------------------------------------
        
        //1> 獲取客戶1
        Customer customer = session.get(Customer.class, 1L);
        
        //2> 獲取待刪除的聯(lián)系人
        Linkman linkeman = session.get(Linkman.class, 3L);
        
        //3> 建立關(guān)系:從客戶的聯(lián)系人集合中刪除該聯(lián)系人
        customer.getLinkemans().remove(linkeman);
        
        //4> 建立關(guān)系:從聯(lián)系人的客戶引用中刪除該客戶
        linkeman.setCustomer(null);
        
        //5> 保存.將瞬時態(tài)對象轉(zhuǎn)換成持久態(tài)對象(customer與linkman都是持久態(tài)對象,無需保存會自動更新)
        //session.save(customer);
        //session.save(linkeman);
        
        //------------------------------------------
        //提交事務(wù)
        transaction.commit();
    }


上面在操作瞬時態(tài)對象時,每個瞬時態(tài)對象都要手動進(jìn)行save保存,為了節(jié)省代碼,Hibernate提供了級聯(lián)操作。只要設(shè)置了級聯(lián)的這一方添加了級聯(lián)配置,與它產(chǎn)生關(guān)系的另一方就無需在進(jìn)行save或者update等操作了。

級聯(lián)測試:


    在客戶的一端設(shè)置級聯(lián)保存,那么只要客戶是持久化狀態(tài),就無需對與其產(chǎn)生關(guān)系的聯(lián)系人進(jìn)行save或者update操作了.

     <!-- 
            級聯(lián)操作:   cascade
                save-update: 級聯(lián)保存更新
                delete:級聯(lián)刪除
                all:save-update+delete
            級聯(lián)操作: 簡化操作.目的就是為了少些兩行代碼.
     -->

    <set name="linkemans" cascade="save-update">
        <key column="lkm_cust_id"></key>
        <one-to-many class="com.itdream.domain.Linkman"/>
    </set>  


    
    @Test
    //保存一個客戶與兩個聯(lián)系人(級聯(lián)保存)
    //為了簡化代碼操作,在做session.save時,save客戶就可以同時將客戶中的ecustomer對象保存.
    //但是,如果直接省略,會報(bào)錯:瞬時態(tài)與執(zhí)行態(tài)關(guān)聯(lián)的錯誤.因此在需要做級聯(lián)保存的映射文件中配置,將cascad屬性設(shè)為save-update即可
    public void test1() {
        //獲取Session對象
        Session session = HibernateUtils.getCurrentSession();
        //開啟事務(wù)
        Transaction transaction = session.beginTransaction();
        //------------------------------------------
        
        //1> 創(chuàng)建一個客戶
        Customer customer = new Customer();
        customer.setCust_name("阿里巴巴");
        
        //2> 創(chuàng)建兩個聯(lián)系人
        Linkman linkeman = new Linkman();
        linkeman.setLkm_name("小馬");
        
        Linkman linkeman2 = new Linkman();
        linkeman2.setLkm_name("小云");
        
        //3> 建立關(guān)系:將聯(lián)系人添加到客戶的集合中
        customer.getLinkemans().add(linkeman);
        customer.getLinkemans().add(linkeman2);
        
        //4> 建立關(guān)系:將客戶添加到聯(lián)系人中
        linkeman.setCustomer(customer);
        linkeman2.setCustomer(customer);
        
        //5> 保存.將瞬時態(tài)對象轉(zhuǎn)換成持久態(tài)對象
        session.save(customer);
        //在customer的映射文件中配置級聯(lián)保存,在保存customer的時候會自動保存與它產(chǎn)生關(guān)聯(lián)的聯(lián)系人
        // session.save(linkeman);
        // session.save(linkeman2);
        
        //------------------------------------------
        //提交事務(wù)
        transaction.commit();
    }
    
    
    @Test
    //為添加一個聯(lián)系人(級聯(lián)保存)
    //為了簡化代碼操作,在做session.save時,save客戶就可以同時將客戶中的ecustomer對象保存.
    //但是,如果直接省略,會報(bào)錯:瞬時態(tài)與執(zhí)行態(tài)關(guān)聯(lián)的錯誤.因此在需要做級聯(lián)保存的映射文件中配置,將cascad屬性設(shè)為save-update即可
    public void test2() {
        //獲取Session對象
        Session session = HibernateUtils.getCurrentSession();
        //開啟事務(wù)
        Transaction transaction = session.beginTransaction();
        //------------------------------------------
        
        //1> 獲取客戶1
        Customer customer = session.get(Customer.class, 1L);
        //2> 創(chuàng)建一個聯(lián)系人
        Linkman linkeman = new Linkman();
        linkeman.setLkm_name("小白");
        
        //3> 建立關(guān)系:將聯(lián)系人添加到客戶的集合中
        customer.getLinkemans().add(linkeman);
        
        //4> 建立關(guān)系:將客戶添加到聯(lián)系人中
        linkeman.setCustomer(customer);
        
        //5> 保存.customer通過get方法取出就是持久態(tài)了,無需手動保存
        //因?yàn)榕渲昧思壜?lián)保存,保存customer的時候Hibernate會自動將與這個customer產(chǎn)生關(guān)系的linkman設(shè)為持久態(tài)保存
        //session.save(customer);
        
        //------------------------------------------
        //提交事務(wù)
        transaction.commit();
    }
    
    
    @Test
    //為客戶1刪除聯(lián)系人id為1的小馬,增加一個聯(lián)系人為小宋
    public void test4() {
        //獲取Session對象
        Session session = HibernateUtils.getCurrentSession();
        //開啟事務(wù)
        Transaction transaction = session.beginTransaction();
        //-------------------------------
        //實(shí)現(xiàn)業(yè)務(wù)邏輯 : 為客戶1刪除聯(lián)系人id為1的小馬,增加一個聯(lián)系人為小宋
        //1> 獲取客戶1
        Customer customer = session.get(Customer.class, 1L);
        
        //2> 獲取要刪除的聯(lián)系人小馬
        Linkman linkman = session.get(Linkman.class, 1L);
        
        //3> 創(chuàng)建一個聯(lián)系人小宋
        Linkman linkman2 = new Linkman();
        linkman2.setLkm_name("小宋");
        
        //4> 建立關(guān)系: 在客戶1方建立對聯(lián)系人的關(guān)系
        //從聯(lián)系人集合中刪除要刪除的聯(lián)系人
        customer.getLinkemans().remove(linkman);
        //添加要添加的聯(lián)系人到聯(lián)系人集合
        customer.getLinkemans().add(linkman2);
        
        //5> 建立關(guān)系: 在聯(lián)系人放建立對客戶1的關(guān)系
        //解除客戶在聯(lián)系人的引用
        linkman.setCustomer(null);
        //在添加的聯(lián)系人的客戶引用中添加客戶1
        linkman2.setCustomer(customer);
        
        //6> 保存
        //customer是持久態(tài),刪除的聯(lián)系人也是持久態(tài),待添加的聯(lián)系人不是持久態(tài),但是由于customer設(shè)置了級聯(lián)保存因此也無需save保存
        //-------------------------------
        //提交事務(wù)
        transaction.commit();
        //關(guān)閉資源,getCurrentSession方法獲得與線程綁定的Session對象無需手動關(guān)閉
    }
    
    
    @Test
    //級聯(lián)刪除 : 在實(shí)際開發(fā)中基本不用,這里測試使用
    //目的:在刪除聯(lián)系人的時候?qū)⑺目蛻粢矂h除掉
    public void test5() {
        //獲取Session對象
        Session session = HibernateUtils.getCurrentSession();
        //開啟事務(wù)
        Transaction transaction = session.beginTransaction();
        //-------------------------------
        //實(shí)現(xiàn)業(yè)務(wù)邏輯 : 刪除聯(lián)系人1的時候?qū)⑺目蛻粢矂h除掉
        //1> 獲取要刪除的聯(lián)系人
        Linkman linkman = session.get(Linkman.class, 1L);
        
        //2> 刪除聯(lián)系人
        session.delete(linkman);
        
        //3> 保存
        //linkman是持久態(tài),無需手動保存,同時它設(shè)置了級聯(lián)刪除,因此刪除聯(lián)系人的同時,會將它對應(yīng)的客戶1也刪除掉
        //如果這個時候在客戶的一方也設(shè)置級聯(lián)刪除,會導(dǎo)致刪除一個客戶或者刪除一個聯(lián)系人,會將與它有關(guān)聯(lián)的所有數(shù)據(jù)都刪除掉
        //-------------------------------
        //提交事務(wù)
        transaction.commit();
        //關(guān)閉資源,getCurrentSession方法獲得與線程綁定的Session對象無需手動關(guān)閉
    }

上面通過級聯(lián)的配置,節(jié)省了一些多余代碼的操作,但是我們通過查看Hibernate打印的sql語句發(fā)現(xiàn)(例如執(zhí)行test1的代碼),Hibernate會對外鍵lkm_cst_id進(jìn)行多余的兩次操作.Linkman自身在insert添加新聯(lián)系人時已經(jīng)維護(hù)了外鍵lkm_cst_id,那么下面update進(jìn)行的維護(hù)很顯然就是客戶customer在進(jìn)行維護(hù),這是很冗余的操作。因此我們需要通過配置進(jìn)行優(yōu)化,Hibernate提供了inverse的屬性來設(shè)置。

img21.png

      <!-- inverse屬性: 配置關(guān)系是否維護(hù). 
            true: customer不維護(hù)關(guān)系
            false(默認(rèn)值): customer維護(hù)關(guān)系
            
        inverse屬性: 性能優(yōu)化.提高關(guān)系維護(hù)的性能.
        原則: 無論怎么放棄,總有一方必須要維護(hù)關(guān)系.
        一對多關(guān)系中: 一的一方放棄.也只能一的一方放棄.多的一方不能放棄.
      -->

    <set name="linkemans" cascade="save-update" inverse="true">
            <key column="lkm_cust_id"></key>
            <one-to-many class="com.itdream.domain.Linkman"/>
    </set>


    //操作進(jìn)階--關(guān)系維護(hù)屬性
    public class Demo3 {
        @Test
        //保存客戶 以及客戶 下的聯(lián)系人
        public void fun1(){
            //1 獲得session
            Session session = HibernateUtils.openSession();
            //2 開啟事務(wù)
            Transaction tx = session.beginTransaction();
            //-------------------------------------------------
            //3操作
            Customer c = new Customer();
            c.setCust_name("阿里巴巴");
            
            LinkMan lm1 = new LinkMan();
            lm1.setLkm_name("小馬");
            
            LinkMan lm2 = new LinkMan();
            lm2.setLkm_name("小云");
            
            //表達(dá)一對多,客戶下有多個聯(lián)系人. 
            // 如果客戶放棄維護(hù)與聯(lián)系人的關(guān)系. 維護(hù)關(guān)系的代碼可以省略
            //c.getLinkMens().add(lm1);
            //c.getLinkMens().add(lm2);
            
            //表達(dá)多對多,聯(lián)系人屬于哪個客戶
            lm1.setCustomer(c);
            lm2.setCustomer(c);
            
            //如果Linkman的配置文件中配置了級聯(lián)保存,則這條保存客戶的語句可以省略
            //session.save(c);
            session.save(lm1);
            session.save(lm2);
            
            //-------------------------------------------------
            //4提交事務(wù)
            tx.commit();
            //5關(guān)閉資源
            session.close();
        }

上面的配置表示:customer放棄外鍵維護(hù)lkm_cst_id.那么同樣再執(zhí)行test1的代碼時,下面update的冗余的sql語句就消失了,達(dá)到了優(yōu)化的目的.

img22.png

二、 多對多的ORM關(guān)系映射

ORM : Object Relational Mapping 。 對象 關(guān)系 映射。

  1. 首先完成Relational數(shù)據(jù)庫表的多對多的關(guān)系表達(dá)(外鍵)
  2. 完成Object實(shí)體對象的多對多關(guān)系表達(dá)(分別創(chuàng)建Set集合存儲對方的對象)
  3. 完成Mapping映射文件中一對多關(guān)系的映射配置(配置)

多對多的實(shí)例

用戶與角色(崗位) User & Role

    一個用戶可以在公司中擔(dān)任多個崗位
    一個崗位上也有多個員工用戶在工作
1.jar包上面一對多的例子已經(jīng)導(dǎo)入
2.創(chuàng)建多對多的兩個數(shù)據(jù)庫表,進(jìn)行關(guān)系表達(dá) Relational
    
    多對多的數(shù)據(jù)庫多表關(guān)系表達(dá),需要創(chuàng)建一個第三方表,這個第三方表至少有兩個字段,分別指向兩張表的主鍵.

    
    -- 用戶表

    CREATE TABLE `sys_user` (
      `user_id` bigint(32) NOT NULL AUTO_INCREMENT COMMENT '用戶id',
      `user_code` varchar(32) NOT NULL COMMENT '用戶賬號',
      `user_name` varchar(64) NOT NULL COMMENT '用戶名稱',
      `user_password` varchar(32) NOT NULL COMMENT '用戶密碼',
      `user_state` char(1) NOT NULL COMMENT '1:正常,0:暫停',
      PRIMARY KEY (`user_id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8;

    
    -- 用戶角色表. 
    
    CREATE TABLE `sys_role` (
      `role_id` bigint(32) NOT NULL AUTO_INCREMENT,
      `role_name` varchar(32) NOT NULL COMMENT '角色名稱',
      `role_memo` varchar(128) DEFAULT NULL COMMENT '備注',
      PRIMARY KEY (`role_id`)
    ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;


    -- 第三方表,負(fù)責(zé)維護(hù)user與role表的關(guān)系
    
    CREATE TABLE `sys_user_role` (
      `role_id` bigint(32) NOT NULL COMMENT '角色id',
      `user_id` bigint(32) NOT NULL COMMENT '用戶id',
      PRIMARY KEY (`role_id`,`user_id`),
      KEY `FK_user_role_user_id` (`user_id`),
      CONSTRAINT `FK_user_role_role_id` FOREIGN KEY (`role_id`) REFERENCES `sys_role` (`role_id`) ON DELETE NO ACTION ON UPDATE NO ACTION,
      CONSTRAINT `FK_user_role_user_id` FOREIGN KEY (`user_id`) REFERENCES `sys_user` (`user_id`) ON DELETE NO ACTION ON UPDATE NO ACTION
    ) ENGINE=InnoDB DEFAULT CHARSET=utf8;

3.創(chuàng)建多對多的兩個實(shí)體類,進(jìn)行關(guān)系表達(dá) Object

    /**
     * 持久化類:用戶實(shí)體類
     */
    public class User {
    
        // 普通屬性
        private Long user_id;//外鍵
        private String user_code;
        private String user_name;
        private String user_password;
        private Character user_state;
    
        // 建立多對多關(guān)系:Set集合存儲當(dāng)前User對象持有的Role角色信息
        private Set<Role> roles = new HashSet<>();



    /**
     * 持久化類:用戶角色的實(shí)體類
     */
    public class Role {
    
        // 普通屬性
        private Long role_id;//外鍵
        private String role_name;
        private String role_memo;
    
        // 建立實(shí)體類多對多關(guān)系:Set集合存儲屬于該Role角色的所有User對象
        private Set<User> users = new HashSet<>();
4.配置兩個映射文件,進(jìn)行關(guān)系表達(dá) Mapping

    User.hbm.xml映射文件配置多對多關(guān)系:

    <!-- 多對多的關(guān)系映射配置 --> 
    <!-- 
        name: 集合屬性名
        table: 配置中間表名
        
        key
         |-column:外鍵,"我"的外鍵列名
         
        class: 我與哪個類是多對多關(guān)系
        column:外鍵.與我產(chǎn)生多對多關(guān)系的外鍵列名
     -->
    <set name="roles" table="sys_user_role">
        <key column="user_id"></key>
        <many-to-many class="com.itdream.domain.Role" column="role_id" ></many-to-many>
    </set>

    ------------------------------------------------------------

    Role.hbm.xml映射文件配置多對多關(guān)系:

    <!-- 多對多的關(guān)系映射配置 --> 
    <!-- 
        name: 集合屬性名
        table: 配置中間表名
        
        key
         |-column:外鍵,"我"的外鍵列名
         
        class: 我與哪個類是多對多關(guān)系
        column:外鍵.與我產(chǎn)生多對多關(guān)系的外鍵列名
     -->
    <set name="roles" table="sys_user_role">
        <key column="user_id"></key>
        <many-to-many class="com.itdream.domain.Role" column="role_id" ></many-to-many>
    </set>

4.在hibernate.cfg.xml中加載映射文件:

    <mapping resource="com/itdream/domain/User.hbm.xml"/>
    <mapping resource="com/itdream/domain/Role.hbm.xml"/>
4.Hibernate多對多關(guān)系測試

    /**
     * Hibernate多對多關(guān)系測試
     */
    
    @Test
    //多對多關(guān)系:創(chuàng)建兩個用戶,創(chuàng)建三個角色由兩個用戶分配保存
    public void test1() {
        //獲取Session對象
        Session session = HibernateUtils.getCurrentSession();
        //開啟事務(wù)
        Transaction transaction = session.beginTransaction();
        
        //----------------------------
        //實(shí)現(xiàn)業(yè)務(wù)邏輯
        //1> 創(chuàng)建兩個用戶
        User user = new User();
        User user2 = new User();
        user.setUser_name("楊冪");
        user2.setUser_name("唐嫣");
        
        //2> 創(chuàng)建三個角色(崗位)
        Role role = new Role();
        Role role2 = new Role();
        Role role3 = new Role();
        role.setRole_name("明星");
        role2.setRole_name("女兒");
        role.setRole_name("妻子");
        
        //3> 用戶表達(dá)關(guān)系:分別在用戶中添加角色
        //楊冪持有明星,女兒,妻子三個角色
        user.getRoles().add(role);
        user.getRoles().add(role2);
        user.getRoles().add(role3);
        //唐嫣持有明星和女兒兩個角色
        user2.getRoles().add(role);
        user2.getRoles().add(role2);
        
        //4> 角色表達(dá)關(guān)系:分別在角色中添加用戶
        role.getUsers().add(user);
        role2.getUsers().add(user);
        role3.getUsers().add(user);
        
        role.getUsers().add(user2);
        role2.getUsers().add(user2);
        
        //5> 保存
        session.save(user);
        session.save(user2);
        session.save(role);
        session.save(role2);
        session.save(role3);
        
        //----------------------------
        //提交事務(wù)
        transaction.commit();
    }

    ==================================================

    我們按照非常正規(guī)的方式,分別建立了user和role對象然后逐個保存,但是就是這么看起來正確的操作卻報(bào)錯了。

    org.hibernate.exception.ConstraintViolationException: could not execute statement

    Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: 
        Duplicate entry '1-1' for key 'PRIMARY'

    根據(jù)日志錯誤信息,我們知道產(chǎn)生了重復(fù)的主鍵:1-1.

    這是因?yàn)槲覀兊牡谌奖碇挥袃蓚€字段,就是兩張表的外鍵,這張表的主鍵就是兩個外鍵組成的聯(lián)合主鍵。
    那為什么會產(chǎn)生重復(fù)的主鍵1-1呢?

    我們在上面inverse外鍵維護(hù)關(guān)系的屬性時講了,inverse默認(rèn)值是false,默認(rèn)維護(hù)外鍵.
    我們在建立關(guān)系的時候,user一方建立了關(guān)系-getRoles.add(user).role一方也建立了關(guān)系-getUsers.add(role);
    這樣它們在維護(hù)外鍵時會導(dǎo)致user維護(hù)了外鍵往第三方表插入了1 1. role在維護(hù)外鍵時也會往第三方表插入1 1.導(dǎo)致聯(lián)合主鍵重復(fù)了。

    ======================================================

    要解決這一問題,有兩種方式:

    方式1: 只讓一方建立關(guān)系
        即: 注釋掉一方的建立關(guān)系,例如注釋掉role方建立與user的關(guān)系表達(dá)
        將 role.getUsers.add(role)的語句全都注釋掉就可以了。
        但是這種方式,我們看起來不是很舒服,因?yàn)楦杏X不夠符合邏輯。
        建立關(guān)系應(yīng)該是兩方都建立。因此就可以使用方式2.

    --------------------------------------------------------

    方式2: 讓被動的一方放棄外鍵維護(hù)
    在這里因?yàn)閡ser是主動選擇的role角色,因此讓role放棄外鍵維護(hù)

    
    <!-- 使用inverse屬性
                true: 放棄維護(hù)外鍵關(guān)系
                false(默認(rèn)值):維護(hù)關(guān)系
                
        結(jié)論: 將來在開發(fā)中,如果遇到多對多關(guān)系.一定要選擇一方放棄維護(hù)關(guān)系.
             一般誰來放棄要看業(yè)務(wù)方向(誰處于被動狀態(tài)). 例如錄入員工時,需要為員工指定所屬角色,角色是被動的.
             那么業(yè)務(wù)方向就是由員工維護(hù)角色. 角色不需要維護(hù)與員工關(guān)系.角色放棄維護(hù)。
             -->        

    <set name="users" table="sys_user_role" inverse="true" >
        <key column="role_id" ></key>
        <many-to-many class="User" column="user_id" ></many-to-many>
    </set>
    

設(shè)置級聯(lián)保存

在上面的測試中,在保存瞬時態(tài)對象到一級緩存中時,需要將每一個瞬時態(tài)對象都一一save,為了節(jié)省代碼,我們使用級聯(lián)保存來解決。


    // 5> 保存
    //正常操作需要save保存每一個瞬時態(tài)對象,為了節(jié)省這些代碼,我們使用級聯(lián)保存cascade
    //分別在User.hbm.xml與Role.hbm.xml映射文件中設(shè)置級聯(lián)配置

    <!-- 使用inverse屬性
        true: 放棄維護(hù)外鍵關(guān)系
        false(默認(rèn)值):維護(hù)關(guān)系
     -->    
    Role.hbm.xml :  
    <set name="users" table="sys_user_role" inverse="true" >
        <key column="role_id" ></key>
        <many-to-many class="User" column="user_id" ></many-to-many>
    </set>

    User.hbm.xml : 
    <set name="roles" table="sys_user_role" cascade="save-update" >
            <key column="user_id" ></key>
            <many-to-many class="Role" column="role_id" ></many-to-many>
    </set>

    ==================================================================

    //這樣設(shè)置之后,只需要保存其中一個對象都能級聯(lián)將其它瞬時對象全都保存.(要保證所有的對象都能被關(guān)聯(lián)到)
    //例如,保存user,會級聯(lián)保存與它建立關(guān)系的role,role2,role3.而role,role2,與user和uer2產(chǎn)生了關(guān)聯(lián),因此user2也成功保存
    //又如,保存role3,會級聯(lián)保存與role3建立了關(guān)系的user2,user2會級聯(lián)保存role和role2,role和role2又能級聯(lián)保存user
    session.save(user);
    // session.save(user2);
    // session.save(role);
    // session.save(role2);
    // session.save(role3);

為用戶添加角色:


    //為唐嫣增加一個角色 : 女神
    @Test
    public void test2() {
        //獲得Session對象
        Session session = HibernateUtils.getCurrentSession();
        //開啟事務(wù)
        Transaction transaction = session.beginTransaction();
        //----------------------------------
        //實(shí)現(xiàn)業(yè)務(wù)邏輯
        //1> 獲取user對象唐嫣
        User user = session.get(User.class, 2L);
        
        //2> 創(chuàng)建一個新的角色:女神
        Role role = new Role();
        role.setRole_name("女神");
        
        //3> user表達(dá)關(guān)系: 將新建的角色添加到user對象的角色集合中
        user.getRoles().add(role);
        
        //4> role表達(dá)關(guān)系 : 將user對象添加到role對象的用戶集合中
        role.getUsers().add(user);
        
        //5> 保存轉(zhuǎn)化為持久態(tài)
        // user對象已經(jīng)是持久態(tài)了,無需手動保存,又因?yàn)樘砑恿思壜?lián)保存,所以會級聯(lián)保存role對象
        
        //----------------------------------
        //提交事務(wù)(事務(wù)提交時,數(shù)據(jù)更新到數(shù)據(jù)庫,清空一級緩存區(qū))
        transaction.commit();
    }

為唐嫣解除明星角色


    @Test
    //為唐嫣解除明星角色
    public void test3() {
        //獲取Session對象
        Session session = HibernateUtils.getCurrentSession();
        //開啟事務(wù)
        Transaction transaction = session.beginTransaction();
        //---------------------------------------------
        //處理業(yè)務(wù)邏輯
        //1> 獲取User對象唐嫣:id為2
        User user = session.get(User.class, 2L);
        
        //2> 獲取Role對象明星:id為2
        Role role = session.get(Role.class, 2L);
        
        //3> User表達(dá)關(guān)系:從role集合中移除明星角色
        user.getRoles().remove(role);
        
        //4> Role表達(dá)關(guān)系:從user集合中移除用戶唐嫣
        role.getUsers().remove(user);
        
        //5> 保存,將瞬時態(tài)對象轉(zhuǎn)換為持久態(tài)
        //user與role對象已經(jīng)持久態(tài)了
        
        //---------------------------------------------
        //提交事務(wù)
        transaction.commit();
    }

cascade與inverse使用總結(jié):

  • cascade : 實(shí)質(zhì)上是為了節(jié)省代碼操作

    cascade級聯(lián)操作:
        save-update: 級聯(lián)保存更新
        delete:級聯(lián)刪除
        all:級聯(lián)保存更新+級聯(lián)刪除
        結(jié)論: cascade簡化代碼書寫.該屬性使不使用無所謂. 建議要用只用save-update.
        使用delete操作太過危險(xiǎn).尤其在多對多中.不建議使用.
  • inverse : 減少多余的sql語句,優(yōu)化Hibernate的性能
    
    雙向建立關(guān)系時,雙方都進(jìn)行外鍵維護(hù)產(chǎn)生了多余的sql語句,使用inverse屬性使一方放棄外鍵維護(hù)。

    使用inverse屬性
        true: 放棄維護(hù)外鍵關(guān)系
        false(默認(rèn)值):維護(hù)關(guān)系
    
    結(jié)論: 
        在一對多關(guān)系中,通常選擇一的一方放棄外鍵維護(hù)。

        多對多關(guān)系中.一定要選擇一方放棄維護(hù)關(guān)系.
         一般誰來放棄要看業(yè)務(wù)方向(被動一方放棄維護(hù)). 例如錄入員工時,需要為員工指定所屬角色.
         那么業(yè)務(wù)方向就是由員工維護(hù)角色. 角色不需要維護(hù)與員工關(guān)系.角色放棄維護(hù)   
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,565評論 6 539
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,115評論 3 423
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,577評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,514評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,234評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,621評論 1 326
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,641評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,822評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,380評論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,128評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,319評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,879評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,548評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,970評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,229評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,048評論 3 397
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,285評論 2 376

推薦閱讀更多精彩內(nèi)容