天天看點

hibernate之xml映射檔案關系維護,懶加載,級聯

一:關系維護

   --->inverse預設值false,表示不放棄關系的維護。  

  --->inverse="true"配置在那一端,表示那一端xml對應的po放棄關系的維護(交由hibernate内部進行維護),由另一端進行關系維護。

 例子:學生班級模型【多對一模型】一端放棄關系的維護,由學生端進行關系維護             

    ----->班級的xml映射檔案中<set name="students" class="Student" inverse="true">          

   學生課程模型【多對多模型】誰修改的頻繁,誰放棄關系的維護。綜合考量。學生端放棄關系的維護     

    ----->學生的xml映射檔案中<set name="courses" table="student_course" inverse="true" >

   [1]放棄關系的維護方,在代碼中無法建立關系,無法斷掉關系。  

   [2]關系的維護方,無論更新,建立,删除,都要在代碼中展現關系的維護。

   [3]如果映射到資料庫表中自己屬性id的主鍵,在别的表裡是外鍵,在删除和更新操作過程中,需要代碼進行斷開和更新操作,來防止關系的丢失和關系阻礙執行(爆出異常)。

  [4]如果沒有設定級聯操作(一般不會設定級聯操作),當删除某個對象時,對象中的某個屬性(非主鍵性屬性)映射表中的字段是另一張表的主鍵,删除操作無需考慮關系(不用去代碼操作斷開),直接可以删除該對象。sql同樣。

  

二:懶加載lazy  

   --->懶加載的從屬對象,在js代碼中不能觸發代理對象,去資料庫查詢真實對象。

    --->在Web應用程式中,系統的瓶頸常在于系統的響應速度。         

    --->Web應用程式做的最多就是和背景資料庫互動,而查詢資料庫是種非常耗時的過程。當資料庫裡記錄過多時,查詢優化更顯得尤為重要。為了解決這種問題,有人提出了緩存的概念,緩存就是将使用者頻繁使用的資料放在記憶體中以便快速通路。在使用者執行一次查詢操作後,查詢的記錄會放在緩存中。當使用者再次查詢時,系統會首先從緩存中讀取,如果緩存中沒有,再查詢資料庫。緩存技術在一定程度上提升了系統性能,但是當資料量過大時,緩存就不太合适了。因為記憶體容量有限,把過多的資料放在記憶體中,會影響電腦性能。而另一種技術,懶加載可以解決這種問題。

     --->延遲加載機制是為了避免一些無謂的性能開銷而提出來的,所謂延遲加載就是當在真正需要資料的時候,才真正執行資料加載操作。在Hibernate中提供了對實體對象的延遲加載以及對集合的延遲加載,另外在Hibernate3中還提供了對屬性的延遲加載。

load-->宿主對象,存在懶加載問題。session未關閉前,代理宿主對象,有效。關閉後,無效。  

get--->不存在懶加載問題。直接從資料庫把資料加載到記憶體中。宿主和從屬對象都包含。

  【不同點】:get方法查詢,不存在懶加載,如果id對應的記錄不存在,則傳回null           

       load方法存在懶加載問題,如果id對應的記錄不存在,則抛出異常。

 【共同點】:先從一級緩存和二級緩存中查找,如果找不到,才去資料庫中查找

     load-->宿主對象懶加載。  

    lazy屬性在po.hbm.xml的關聯關系配置中--->對宿主的從屬對象進行懶加載。session未關閉前,從屬代理對象有效,可取出資料。session關閉後,從屬對象失效。若取資料,報錯。

所謂懶加載(lazy)就是延時加載,延遲加載。 什麼時候用懶加載呢,我隻能回答要用懶加載的時候就用懶加載。

至于為什麼要用懶加載呢,就是當我們要通路的資料量過大時,明顯用緩存不太合适,

因為記憶體容量有限 ,為了減少并發量,減少系統資源的消耗,

我們讓資料在需要的時候才進行加載,這時我們就用到了懶加載。

比如部門ENTITY和員工ENTITY,部門與員工1對多,如果lazy設定為 false,那麼隻要加載了一個部門的po,就會根據一對多配置的關系把所有員工的po也加載出來。但是實際上有時候隻是需要用到部門的資訊,不需要用到 員工的資訊,這時員工po的加載就等于浪費資源。如果lazy設定為true,那麼隻有當你通路部門po的員工資訊時候才回去加載員工的po的資訊。

hibernate3.0中lazy有三個值,true,false,proxy,預設的是lazy="proxy". 具體設定成什麼要看你的需求,并不是說哪個設定就是最好的。 假如在student對象中包含一個head對象 如果你确定在用student對象的時候就要用到head對象裡的屬性,那你就設定立即加載,因為設定立即加載那麼在查詢student的同時就會查詢 student的head,hibernate就會在查詢的時候關聯兩張表進而生成的sql就可能隻有一條。而如果你設定的是延遲加載,那麼肯定會要生成 1+N條sql語句:其中“1”是查詢student的語句,“N”是根據N個student的id去查詢head的N條語句。而且,延遲加載是要用到的 時候才去執行查詢,這樣系統判斷那裡需要加載,那裡不需要加載也需要時間,性能上肯定就不如立即加載了! 如果,你是有的地方需要用到student的時候才用到head屬性,那麼你就設定成延遲加載,因為查詢2張表的資料肯定要比查詢1張表的資料消耗大。 到低要怎樣設定就要看你的實際需求了

延遲加載機制是為了避免一些無謂的性能開銷而提出來的,所謂延遲加載就是當在真正需要資料的時候,才真正執行資料加載操作。在Hibernate中提供了對實體對象的延遲加載以及對集合的延遲加載,另外在Hibernate3中還提供了對屬性的延遲加載。

A、實體對象的延遲加載

如果想對實體對象使用延遲加載,必須要在實體的映射配置檔案中進行相應的配置,如下所示:

       <hibernate-mapping>  

        <class name=”com.neusoft.entity.User” table=”user” lazy=”true”>     ……  </class>

      </hibernate-mapping>      

  通過将class的lazy屬性設定為true,來開啟實體的延遲加載特性。

    如果我們運作下面的代碼:     

      User user=(User)session.load(User.class,”1”);(1)     

      System.out.println(user.getName());(2)   

      當運作到(1)處時,Hibernate并沒有發起對資料的查詢,如果此時通過一些調試工具,觀察此時user對象的記憶體快照,會驚奇的發現,此時返 回的可能是User$EnhancerByCGLIB$$bede8986類型的對象,而且其屬性為null,這是怎麼回 事?session.load()方法會傳回實體對象的代理類對象,這裡所傳回的對象類型就是User對象的代理類對象。在Hibernate中通過使用 CGLIB,來實作動态構造一個目标對象的代理類對象,并且在代理類對象中包含目标對象的所有屬性和方法,而且所有屬性均被指派為null。通過調試器顯 示的記憶體快照,可以看出此時真正的User對象,是包含在代理對象的CGLIB$CALBACK_0.target屬性中,當代碼運作到(2)處時,此時 調用user.getName()方法,這時通過CGLIB賦予的回調機制,實際上調用CGLIB$CALBACK_0.getName()方法,當調用 該方法時,Hibernate會首先檢查CGLIB$CALBACK_0.target屬性是否為null,如果不為空,則調用目标對象的getName 方法,如果為空,則會發起資料庫查詢,生成類似這樣的SQL語句:select * from user where id=’1’;來查詢資料,并構造目标對象,并且将它指派到CGLIB$CALBACK_0.target屬性中。   這樣,通過一個中間代理對象,Hibernate實作了實體的延遲加載,隻有當使用者真正發起獲得實體對象屬性的動作時,才真正會發起資料庫查詢操作。 是以實體的延遲加載是用通過中間代理類完成的,是以隻有session.load()方法才會利用實體延遲加載,因為隻有session.load()方 法才會傳回實體類的代理類對象。

  B、集合類型的延遲加載   

    在Hibernate的延遲加載機制中,針對集合類型的應用,意義是最為重大的,因為這有可能使性能得到大幅度的提 高,為此Hibernate進行了大量的努力,其中包括對JDK Collection的獨立實作,在一對多關聯中,定義的用來容納關聯對象的Set集合,并不是java.util.Set類型或其子類型,而是 net.sf.hibernate.collection.Set類型,通過使用自定義集合類的實作,Hibernate實作了集合類型的延遲加載。

    為了 對集合類型使用延遲加載,必須如下配置實體類的關于關聯的部分:

        <hibernate-mapping>   

               <class name=”com.neusoft.entity.User” table=”user”>      ……    

                  <set name=”addresses” table=”address” lazy=”true” inverse=”true”>     

                      <key column=”user_id”/>      

                         <one-to-many class=”com.neusoft.entity.Arrderss”/>   

                  </set>   

              </class>

        </hibernate-mapping>   

     通過将<set>元素的lazy屬性設定為true來開啟集合類型的延遲加載特性。看下面的代碼:  

        User user=(User)session.load(User.class,”1”);  

        Collection addset=user.getAddresses();      (1)  

        Iterator it=addset.iterator();               (2)  

        while(it.hasNext()) {   

            Address address=(Address)it.next();   

            System.out.println(address.getAddress());  }      

    當程式執行到(1)處時,并不會發起對關聯資料的查詢來加載關聯資料,隻有運作到(2)處時,真正的資料讀取操作才會開始,這時Hibernate會根據緩存中符合條件的資料索引,來查找符合條件的實體對象。         這裡引入了一個全新的概念——資料索引,下面首先将說明什麼是資料索引。在Hibernate中對集合類型進行緩存時,是分兩部分進行緩存的,首先緩存 集合中所有實體的id清單,然後緩存實體對象,這些實體對象的id清單,就是所謂的資料索引。當查找資料索引時,如果沒有找到對應的資料索引,這時就會一 條select SQL的執行,獲得符合條件的資料,并構造實體對象集合和資料索引,然後傳回實體對象的集合,并且将實體對象和資料索引納入Hibernate的緩存之 中。另一方面,如果找到對應的資料索引,則從資料索引中取出id清單,然後根據id在緩存中查找對應的實體,如果找到就從緩存中傳回,如果沒有找到,在發 起select SQL查詢。在這裡我們看出了另外一個問題,這個問題可能會對性能産生影響,這就是集合類型的緩存政策。

    如果如下配置集合類型:

     <hibernate-mapping>   

      <class name=”com.neusoft.entity.User” table=”user”>   

          …    

        <set name=”addresses” table=”address” lazy=”true” inverse=”true”>       

          <cache usage=”read-only”/>       

          <key column=”user_id”/>       

          <one-to-many class=”com.neusoft.entity.Arrderss”/>    

        </set>  

        </class>

    </hibernate-mapping>    

    這裡應用了<cache usage=”read-only”/>配置,如果采用這種政策來配置集合類型,Hibernate将隻會對資料索引進行緩存,而不會對集合中的實體對象進行緩存。如上配置運作下面的代碼:  

        Collection addset=user.getAddresses();    

          Iterator it=addset.iterator();             

         while(it.hasNext()) {   

            System.out.println(address.getAddress());  

          }

           System.out.println(“Second query……”);  

           User user2=(User)session.load(User.class,”1”);  

           Collection it2=user2.getAddresses();

           while(it2.hasNext()) {   

                Address address2=(Address)it2.next();  

                System.out.println(address2.getAddress());

           }

      運作這段代碼,會得到類似下面的輸出:

     Select * from user where id=’1’;    

      Select * from address where user_id=’1’;    Tianjin    Dalian    Second query……    

      Select * from address where id=’1’;    

      Select * from address where id=’2’;    Tianjin    Dalian     

    可以看到,當第二次執行查詢時,執行了兩條對address表的查詢操作,為什麼會這樣呢?這是因為當第一次加載實體後,根據集合類型緩存政策的配 置,隻對集合資料索引進行了緩存,而并沒有對集合中的實體對象進行緩存,是以在第二次再次加載實體時,Hibernate找到了對應實體的資料索引,但是 根據資料索引,卻無法在緩存中找到對應的實體,是以Hibernate根據找到的資料索引發起了兩條select SQL的查詢操作,這裡造成了對性能的浪費,怎樣才能避免這種情況呢?必須對集合類型中的實體也指定緩存政策,對集合類型進行配置:

      <hibernate-mapping>   

          <class name=”com.neusoft.entity.User” table=”user”>    

             ……      

              <set name=”addresses” table=”address” lazy=”true” inverse=”true”>        

                  <cache usage=”read-write”/>        

                  <key column=”user_id”/>     

                  <one-to-many class=”com.neusoft.entity.Arrderss”/>   

               </set>  

           </class>

      </hibernate-mapping>  

     此時Hibernate會對集合類型中的實體也進行緩存,再次運作上面的代碼,将會得到類似如下的輸出:    

      Select * from user where id=’1’;

      Select * from address where user_id=’1’;    Tianjin    Dalian    Second query……    Tianjin    Dalian   

    這時将不會再有根據資料索引進行查詢的SQL語句,因為此時可以直接從緩存中獲得集合類型中存放的實體對象。

C、屬性延遲加載      

  在Hibernate3中,引入了一種新的特性——屬性的延遲加載,這個機制又為擷取高性能查詢提供了有力的工具。在大資料對象讀取時,假設在User 對象中有一個resume字段,該字段是一個java.sql.Clob類型,包含了使用者的履歷資訊,當加載該對象時,不得不每一次都要加載這個字段,而 不論是否真的需要它,而且這種大資料對象的讀取本身會帶來很大的性能開銷。在Hibernate2中,隻有通過面向性能的粒度細分,來分解User類,來 解決這個問題,但是在Hibernate3中,可以通過屬性延遲加載機制,來使我們獲得隻有當我們真正需要操作這個字段時,才去讀取這個字段資料的能力, 為此必須如下配置實體類:

   <hibernate-mapping>   

      <class name=”com.neusoft.entity.User” table=”user”>  

           ……   

          <property name=”resume” type=”java.sql.Clob” column=”resume” lazy=”true”/>   

      </class>  

    </hibernate-mapping>      

  通過對<property>元素的lazy屬性設定true來開啟屬性的延遲加載,在Hibernate3中為了實作屬性的延遲加載,使用 了類增強器來對實體類的Class檔案進行強化處理,通過增強器的增強,将CGLIB的回調機制邏輯,加入實體類,這裡我們可以看出屬性的延遲加載,還是 通過CGLIB來實作的。CGLIB是Apache的一個開源工程,這個類庫可以操縱java類的位元組碼,根據位元組碼來動态構造符合要求的類對象。根據上 面的配置我們運作下面的代碼:

      String sql=”from User user where user.name=’zx’ ”;  

      Query query=session.createQuery(sql);   (1)  

      List list=query.list();  

      for(int i=0;i<list.size();i++) {   

        User user=(User)list.get(i);

         System.out.println(user.getName());

         System.out.println(user.getResume());   (2)

     }   

    當執行到(1)處時,會生成類似如下的SQL語句:  Select id,age,name from user where name=’zx’;   

        這時Hibernate會檢索User實體中所有非延遲加載屬性對應的字段資料,

    當執行到(2)處時,會生成類似如下的SQL語句:  Select resume from user where id=’1’;

        這時會發起對resume字段資料真正的讀取操作。

三:級聯cascade

(1)多表crud過程,簡化操作

(2)操作一個宿主對象時,則将該對象的從屬對象進行同樣的操作。

(3)級聯,在宿主上進行配置cascade屬性。在哪端配置,哪端的對象就是宿主。

(4)在實際開發過程中,一般不用級聯配置,因為可控性差。對程式員要求比較高。

◦ 級聯操作。操作一個對象時将該對象相關屬性對象也進行同樣操作。

  ◦ all:進行任何操作都級聯。

  ◦ save-update:儲存和更新操作時

  ◦ delete:删除操作時級聯

  ◦ all-delete-orpnan:當被關聯對象失去宿主時,将其級聯删除。

  ◦none(預設) :不級聯

四:對象抓取政策fetch

    fetch參數指定了關聯對象抓取的方式是select查詢還是join查詢,select方式時先查詢傳回要查詢的主體對象(清單),再根據關聯外鍵id,每一個對象發一個select查詢,擷取關聯的對象,形成n+1次查詢(會産生兩條以上的sql語句);

而join方式,主體對象和關聯對象用一句外鍵關聯的sql同時查詢出來,不會形成多次查詢。

如果你的關聯對象是延遲加載的,它當然不會去查詢關聯對象。

另外,在hql查詢中配置檔案中設定的join方式是不起作用的(而在所有其他查詢方式如get、criteria或再關聯擷取等等都是有效的),會使用select方式,除非你在hql中指定join fetch某個關聯對象。

fetch政策用于定義 get/load一個對象時,如何擷取非lazy的對象/集合。 這些參數在Query中無效(相當于小型非懶加載)。

在某種特殊的場合下,fetch在hql中還是起作用的。

    例子:學生班級模型

      在Student.hbm.xml中

<many-to-one name="classRoom" class="ClassRoom" fetch="join">

            <column name="classRoom_id"></column>

</many-to-one>

      查詢指定id的學生時,hibernate産生的sql:

   select

        student0_.id as id1_1_,

        student0_.name as name1_1_,

        student0_.sex as sex1_1_,

        student0_.classRoom_id as classRoom4_1_1_,

        classroom1_.id as id0_0_,

        classroom1_.name as name0_0_

    from

        shang_student student0_

    left outer join

        shang_classRoom classroom1_

            on student0_.classRoom_id=classroom1_.id

    where

        student0_.id=?

  在ClassRoom.hbm.xml中

<set name="students" inverse="true" fetch="join">

            <key column="classRoom_id"></key>

            <one-to-many class="Student"/>

</set>

  查詢指定id的班級時,hibernate産生的sql:

select

        classroom0_.id as id0_1_,

        classroom0_.name as name0_1_,

        students1_.classRoom_id as classRoom4_3_,

        students1_.id as id3_,

        students1_.id as id1_0_,

        students1_.name as name1_0_,

        students1_.sex as sex1_0_,

        students1_.classRoom_id as classRoom4_1_0_

        shang_classRoom classroom0_

        shang_student students1_

            on classroom0_.id=students1_.classRoom_id

        classroom0_.id=?