天天看點

Hibernate 知識點總結

HIbernate最佳實踐

1、使用Configuration裝載映射檔案時,不要使用絕對路徑裝載。最好的方式是通過getResourceAsStream()裝載映射檔案,這樣Hibernate會從classpath中尋找已配置的映射檔案。

2、SessionFactory的建立非常消耗資源,整個應用一般隻要一個SessionFactory就夠了,隻有多個資料庫的時候才會使用多個SessionFactory。

3、在整個應用中,Session和事務應該能夠統一管理。(Spring為Hibernate提供了非常好的支援)

4、将所有的集合屬性配置設定為懶加載(lazy=”true”)。在hibernate2.x版本中,lazy預設值是“false”,但hibernate3.x已經将lazy的預設改為“true”了。

5、在定義關聯關系時,集合首選Set,如果集合中的實體存在重複,則選擇List(在定義配置檔案時,可以将List定義為bag),數組的性能最差。

6、在一對多的雙向關聯中,一般在一這端将集合的inverse屬性設定為true,讓集合的對方維護關聯關系。例如:Group-User,由User來維護Group和User的關聯關系,下面詳述。

7、HQL子句本身大小寫無關,但是其中出現的類名和屬性名必須注意大小寫區分。

8、在非分布式架構中,不需要使用DTO來向上層傳輸資料。直接使用POJO的Entity就可以了。

9、如果要精通Hibernate,熟練掌握關系資料庫理論和SQL是前提條件。

10、HQL代碼 > fetch(配置) > lazy (配置)

fetch和lazy關鍵字

Lazy是在擷取對象時是否進行延遲加載,而fetch是在延遲加載的情況下對對象的抓取政策,其值有select,join,subselect和batch fetching,即隻有當Lazy=true時必須有fetch關鍵字否則就會報異常。lazy的值可以設定為true ,false,proxy。兩者的配合使用問題:

1、當lazy="true" fetch = "select" 時,即使用延遲政策,開始隻查詢出一端實體,多端的不會查詢,隻有當用到的時候才會發出sql語句去查詢;

2、當lazy="false" fetch = "select" 時,即沒有用延遲政策,同時查詢出一端和多端,同時産生1+n條sql.

3、當lazy="true"/lazy="false" fetch = "join"的時候,這個時候延遲已經沒有什麼用了,因為采用的是外連接配接查詢,同時把一端和多端都查詢出來了,延遲沒有起作用。

避免N+1次查詢的方法除了以上方法,還可以使用二級緩存,下面緩存一節有詳述

Inner Join 的主要精神就是 exclusive , 叫它做排他性吧! 就是講 Join 規則不相符的資料就會被排除掉

Outer Join:  Select <要查詢的字段> From <Left 資料表> <Left | Right> [Outer] Join <Right 資料表> On <Join 規則> , 文法中的 Outer 是可以省略的, 例如你可以用 Left Join 或是 Right Join, 在本質上, Outer Join 是 inclusive, 叫它做包容性吧! 不同于 Inner Join 的排他性, 是以在 Left Outer Join 的查詢結果會包含所有 Left 資料表的資料, 颠倒過來講, Right Outer Join 的查詢就會包含所有 Right 資料表的資料

注意HQL在使用join時不能使用on關鍵字,可以使用with,而使用Join時,在hbm.xml中設定好對象的關聯主鍵就可以了,否則使用了On就會報下面的錯誤:

from Apostpdas ap left outer join Apostpdaddas op on ap.id=op.apostpdId  right outer join Clintcbasclb on ap.cnum=clb.cnum 老是報Causedby:org.hibernate.hql.ast.QuerySyntaxError:unexpectedtoken:onnearline1,column127[selectcount(*)

通過HQL的with關鍵字,你可以提供額外的join條件。

from Cat

as

cat     left join cat.kittens

as

kitten         with kitten.bodyWeight > 10.0

延遲抓取要注意的問題。在一個打開的Hibernate session上下文之外調用延遲集合會導緻一次意外。比如: 

s = sessions.openSession();
   Transaction tx = s.beginTransaction();            
   User u = (User) s.createQuery("from User u where u.name=:userName").setString("userName", userName).uniqueResult();
   Map permissions = u.getPermissions();
   tx.commit();
   s.close();
   Integer accessLevel = (Integer) permissions.get("accounts");  // Error!
           

在Session關閉後,permessions集合将是未執行個體化的、不再可用,是以無法正常載入其狀态。 

Hibernate對脫管對象不支援延遲執行個體化. 這裡的修改方法是:将permissions讀取資料的代碼 移到tx.commit()之前。 

Fetch抓取政策可以O/R映射檔案中聲明,也在特定的HQL或條件查詢(Criteria Query)中重載聲明, 在映射文檔中定義的抓取政策對HQL無效,而會對以下清單條目産生影響:(1)通過get()或load()方法取得資料。 (2)隻有在關聯之間進行導航時,才會隐式的取得資料。如下例為在Criteria Query中使用抓取政策的例子和在HQL中使用join fetch的例子:

User user = (User) session.createCriteria(User.class)
                .setFetchMode("permissions", FetchMode.JOIN)
                .add(Restrictions.idEq(userId) )
                .uniqueResult();
           

注意Criteria使用setFetchMode, 而HQL使用left join fetch

Parent parent = (Parent)hibernateTemplate.execute(new HibernateCallback() {
public Object doInHibernate(Session session) throws HibernateException, SQLException {
                Query q = session.createQuery(
                 "from Parent as parent "+
                 " left outer join fetch parent.childs " +
                    " where parent.id = :id"
                 );
                q.setParameter("id",new Long(15));
                  return (Parent)q.uniqueResult();
            }
        });
        Assert.assertTrue(parent.getChilds().size() > 0); 
           

HQL和Criteria Query中的查詢政策預設為Select方式,在HQL中指定join Fetch某個關聯對象将使用join的方式(如上例),一般而言,不使用映射檔案定制抓取政策。更多的是,保持其預設值(Select方式),然後在特定的事務中,使用HQL或Criteria Query中的左連接配接抓取(left join fetch) 對其進行重載。這将通知 Hibernate在第一次查詢中使用外部關聯(outer join),直接得到其關聯資料。

如上所說,OR配置檔案中的配置政策會對Session的get和load方法産生影響,下面為使用get和load的例子:

Session session=sessionFactory.openSession();
  tx = session.beginTransaction();
  Customer customer=(Customer)session.get(Customer.class,new Long(1));
  tx.commit();
  session.close();
           
tx = session.beginTransaction();
  Customer c1=(Customer)session.load(Customer.class,new Long(1));
  Customer c2=(Customer)session.load(Customer.class,new Long(1));
  System.out.println(c1==c2);
  tx.commit();
  session.close();
           

關于Get和load還有一點差別: hibernate對于load方法認為該資料在資料庫中一定存在,可以放心的使用代理來延遲加載,如果在使用過程中發現了問題,隻能抛異常;而對于get方法,hibernate一定要擷取到真實的資料,否則傳回null。 

除了以上介紹的join和select方式的抓取政策,還有另外兩種抓取政策: 

單端代理的批量抓取:<many-to-one name="classes" column="classesid" fetch="select"/>

集合代理的批量抓取:<set name="students" inverse="true" cascade="all" fetch="select">

使用場景:

Query query = session.createQuery("from Classes where id in(1,2,3)");
List<Classes> list = query.list();
for(Classes c : list){
    for(Iterator i = c.getStudents().iterator();i.hasNext();){
        Student s = (Student) i.next();
        System.out.println("姓名:"+s.getName()+"班級:"+c.getClassName());
    }
}
           

子查詢抓取(fetch="subselect") - 另外發送一條SELECT 語句抓取在前面查詢到(或者抓取到)的所有實體對象的關聯集合。除非你顯式的指定lazy="false" 禁止延遲抓取(lazy fetching),否則隻有當你真正通路關聯關系的時候,才會執行第二條select語句。 假若一個延遲集合或單值代理需要抓取,Hibernate會使用一個subselect重新運作原來的查詢,一次性讀入所有的執行個體。這和批量抓取的實作方法是一樣的,不會有破碎的加載。

批量抓取(Batch fetching) - 對查詢抓取的優化方案, 通過指定一個主鍵或外鍵清單,Hibernate使用單條SELECT語句擷取一批對象執行個體或集合。

批量抓取是延遲查詢抓取的優化方案,你可以在兩種批量抓取方案之間進行選擇:在類級别和集合級别。 

類級别的批量加載實體<class name="Classes" table="t_classes" batch-size="3"> 類上的批量抓取 from Student s where id in(1,11,21)

集合級别的批量加載實體 <set name="students" inverse="true" cascade="all" batch-size="5"> 集合上的批量抓取 from Classes c where id in(1,2,3)

類/實體級别的批量抓取很容易了解。假設你在運作時将需要面對下面的問題:你在一個Session中載入了25個Cat執行個體,每個Cat執行個體都擁有一個引用成員owner,其指向Person,而Person類是代理,同時lazy="true"。 如果你必須周遊整個cats集合,對每個元素調用getOwner()方法,Hibernate将會預設的執行25次SELECT查詢, 得到其owner的代理對象。這時,你可以通過在映射檔案的Person屬性,顯式聲明batch-size,改變其行為:

<class name="Person" batch-size="10">...</class>

随之,Hibernate将隻需要執行三次查詢,分别為10、10、 5。 

你也可以在集合級别定義批量抓取。例如,如果每個Person都擁有一個延遲載入的Cats集合, 現在,Sesssion中載入了10個person對象,周遊person集合将會引起10次SELECT查詢, 每次查詢都會調用getCats()方法。如果你在Person的映射定義部分,允許對cats批量抓取, 那麼,Hibernate将可以預先抓取整個集合。請看例子:

<class name="Person">

    <set name="cats" batch-size="3">

        ...

    </set>

</class>

如果整個的batch-size是3,那麼Hibernate将會分四次執行SELECT查詢, 按照3、3、3、1的大小分别載入資料。

這裡的每次載入的資料量還具體依賴于目前Session中未執行個體化集合的個數。 

cascade 和 inverse 

cascade 和 inverse 主要是用來級聯插入和修改的 cascade主要是簡化了在代碼中的級聯更新和删除。隻有集合标記(set/map/list/array/bag)才有inverse屬性,隻有“關系标記”才有cascade屬性:many-to-one,one-to-one ,any。

其實Inverse的意思是反轉,inverse的預設值是false,即不反轉,即控制權屬于false的一方,是以使用預設值,雙方都要進行控制,但是對于一對多的情況下,如果一這一端進行控制,對造成性能上的限制,是以将一這一方的inverse設定為true,則由多方來進行控制。如下面的一對多的配置中,一方是events,多方是participants,在events的配置中将關系的inverse設定為true,則participants将來維護這個一對多的關系。

<class name="events.Event" table="events">  
      <id name="id" column="event_id">  
            <generator class="native"/>  
      </id>  
      <property name="date" column="events_date" type="timestamp"/>  
      <property name="title" column="events_title"/>  
      <set name="participants" table="person_event" inverse="true">  
          <key column="event_id"/>  
          <many-to-many column="person_id" class="events.Person"/>  
      </set>  
  </class>  
           

所謂的關系,一般是指外鍵關聯的關系,而維護關系,是指通過與cacade協作,共同維護好關聯關系,保持資料的一緻性, cacade的配置項:

  • none - do not do any cascades, let the users handles them by themselves.
  • save-update - when the object is saved/updated, check the associations and save/update any object that require it (including save/update the associations in many-to-many scenario).
  • delete - when the object is deleted, delete all the objects in the association.
  • delete-orphan - when the object is deleted, delete all the objects in the association. In addition to that, when an object is removed from the association and not associated with another object (orphaned), also delete it.
  • all - when an object is save/update/delete, check the associations and save/update/delete all the objects found.
  • all-delete-orphan - when an object is save/update/delete, check the associations and save/update/delete all the objects found. In additional to that, when an object is removed from the association and not associated with another object (orphaned), also delete it.

cascade:在對主要方操作時,級聯發生,而這個主要方是inverse=false的一方。 inverse: 在flush時(commit會自動執行flush),對session中的所有set,hibernate判斷每個set是否有變化,對有變化的set執行相應的sql,執行之前,會有個判斷:if( inverse == true ) return; 可以看出cascade在先,inverse在後。

關于cascade和inverse的詳細介紹, 還是看stackoverflow上的答案吧:cascade和inverse的差別

1. inverse: This is used to decide which side is the relationship owner to manage the relationship (insert or update of the foreign key column).

2. cascade: In cascade, after one operation (save, update and delete) is done, it decide whether it need to call other operations (save, update and delete) on another entities which has relationship with each other.

Conclusion: In short, the “inverse” is decide which side will update the foreign key, while “cascade” is decide what’s the follow by operation should execute. Both are look quite similar in relationship, but it’s totally two different things. Hibernate developers are worth to spend time to research on it, because misunderstand the concept or misuse it will bring serious performance or data integrity issue in your application.

測試:一對多關系的兩張表:boy、girl(一個男孩可以多個女朋友)

boy表結構

Field   Type        

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

name    varchar(50)  pk

age     varchar(50)

girl表結構

Field   Type        

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

name    varchar(50)  pk

bf      varchar(50)  fk

【儲存時:Inverse與cascade】

建立三個girl對象和一個boy對象,讓這是三個girl都是boy的女朋友,如下代碼:

Boy boy = new Boy("tom","23", null);
  Set girls = new HashSet();
  Girl g[] = new Girl[]{new Girl("Alice1", boy), new Girl("Alice2", boy),  new Girl("Alice3", boy) };
  girls.add(g[0]);
  girls.add(g[1]);
  girls.add(g[2]);
  boy.setGirls(girls);
  session.save(boy);
           

在Boy.hbm.xml中設定,

1.Inverse = true,不指定cascade 既為none

   cascade的預設值為none, 當對boy進行儲存操作時, girl什麼都不做. 是以隻儲存了boy對象, 沒有儲存girl對象

2.Inverse = true,cascade=all

   boy與girl對象,包括外鍵都成功儲存。隻不過girl表中的對應的id是null,發生SQL語句次數:SELECT 3, INSERT 4,分析:boy控制反轉,關聯關系有girls們維護,但有cascade進行了級聯操作,girls們插入到資料中去了

3.Inverse = false,不指定cascade,既為none

   報錯。因為boy為主要方,負責維護關系,在維護關系是發現并不存在girl記錄, 是以不能建立關系。

4.Inverse = false,cascade=all   (由一方維護關系,那麼會産生update)

   boy與girl對象,包擴外鍵都成功儲存。發生SQL語句次數:SELECT 3, INSERT 4, UPDATE 3。分析:除了4條INSERT語句之外,其他的6條語句中,3條SELECT語句用來判斷girl對象是否在資料表中已經存在,3條UPDATE語句是為了維護外鍵關系

高效率的做法:在Boy.hbm.xml中設定Inverse=true,在Girl.hbm.xml中設定cascade=all,然後儲存三個girl對象,發生SQL語句次數:SELECT 1, INSERT 4

【删除時:Inverse與cascade】希望通過删除boy,也将3個girl對象删除。程式中先查出boy對象,然後進行删除

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

  Boy boy = (Boy) s.get(Boy.class, "tom");

  s.delete(boy);

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

同樣在Boy.hbm.xml中進行設定

1.Inverse = true  cascade = none

   可以猜到結果是出錯。原因:外鍵限制錯誤,他沒有維護關系,是以引起外鍵沖突

2.Inverse = false  cascade = none                

   boy删除,girl表中外鍵變為null,沒有删除記錄,發生SQL語句次數:UPDATE 1, DELETE 1。由此可見,作為主要方如果不指定cascade,将不進行級聯操作,對于外鍵來說,隻是将外鍵約隻為null, 而設定為cascade之後,将進行級聯操作,對于删除來說,是先将外鍵限制改為null, 即先解除外鍵限制,在進行删除,如3.

3.Inverse = false, cascade = all

    全部删除;在删除有外鍵的從表時,先把從表外鍵置為null,然後删除主表記錄,最後根據從表主鍵删除所有相關從表記錄,發生SQL語句次數:UPDATE 1, DELETE 4

4.Inverse = true, cascade = all

    全部删除,發生SQL語句次數:DELETE 4,關于這一點應該是從Girls端進行維護關系,

SQL,HQL,Criteria Query

概述:資料查詢與檢索是Hibernate中的一個亮點。相對其他ORM實作而言,Hibernate提供了靈活多樣的查詢機制。

标準化對象查詢(Criteria Query):以對象的方式進行查詢,将查詢語句封裝為對象操作。優點:可讀性好,符合Java 程式員的編碼習慣。缺點:不夠成熟,不支援投影(projection)或統計函數(aggregation)

Hibernate語言查詢(Hibernate Query Language,HQL):它是完全面向對象的查詢語句,查詢功能非常強大,具備繼承、多态和關聯等特性 。Hibernate官方推薦使用HQL進行查詢。

Native SQL Queries(原生SQL查詢):直接使用資料庫提供的SQL方言進行查詢。

 悲觀鎖和樂觀鎖

Hibernate的鎖機制,悲觀鎖和樂觀鎖。悲觀鎖一般是借助資料庫管理實作的,如Stirng hql=“from user where user.name='sa'”. Query query=session.createQuery(hql);  query.setLockMode("user",LockMode.UPDATE); List list=query.list();

它指的是對資料被外界修改持保守态度。假定任何時刻存取資料時,都可能有另一個客戶也正在存取同一筆資料,為了保持資料被操作的一緻性,于是對資料采取了資料庫層次的鎖定狀态,依靠資料庫提供的鎖機制來實作。

基于jdbc實作的資料庫加鎖如下:

select * from account where name="test" for update

在更新的過程中,資料庫處于加鎖狀态,任何其他的針對本條資料的操作都将被延遲。本次事務送出後解鎖。

而hibernate悲觀鎖的具體實作如下:

String sql="查詢語句";

Query query=session.createQuery(sql);

query.setLockMode("對象别名",LockModel.UPGRADE);

session.load(Person.class, 1,LockMode.UPGRADE);

hibernate的加鎖模式:

LockMode.NONE:無鎖機制。

LockMode.WRITE:Hibernate在Insert和Update記錄的時候會自動擷取。

LockMode.READ:Hibernate在讀取記錄的時候會自動擷取。

這三種加鎖模式是供hibernate内部使用的,與資料庫加鎖無關:

LockMode.UPGRADE:利用資料庫的for update字句加鎖。

隻有在查詢開始之前(也就是hiernate生成sql語句之前)加鎖,才會真正通過資料庫的鎖機制加鎖處理。

樂觀鎖

樂觀鎖是借助一些諸如資料版本的機制實作,比如在資料表上增加一個字段version,如首先在class的屬性上加上optimistic-lock=version,除version之外還有none,dirty和all等,然後配置檔案中加一個version節點,如<version name="version" column="version" type="java.lang.String" 顯然在資料表上增加的vesion字段最終要展現到OR檔案上。

樂觀鎖定(optimistic locking)則樂觀的認為資料的存取很少發生同時存取的問題,因而不作資料庫層次上的鎖定,為了維護正确的資料, 樂觀鎖定采用應用程式上的邏輯實作版本控制的方法。

目前事務如果正在試圖送出一個過期資料,将會抛出StaleObjectStateException異常,通過捕捉這個異常,我

們就可以在樂觀鎖校驗失敗時進行相應處理。Stale陳腐的,顧這個異常的意思是陳腐的對象狀态異常

關聯關系

唯一主鍵關聯(一對一),雙方的class中都有一個one-to-one 指向雙方,且一方的主鍵有另一方主鍵生成,比如<id name='cid' type=java.lang.integer><column name='C_ID'><generator class='assigned'></id>,另一方為<id name='cid' type=java.lang.integer><column name='C_ID'><generator class='foreign'>上方的類路徑<param name='property></param><genetor></id>,

唯一外鍵關聯,如B的主鍵是A的外鍵,則B中使用one-to-one,A中使用many-to-one進行兩者的關聯,因為多個外鍵可以對應一個主鍵,顧外鍵的要使用many-to-one

多對一關聯(雙向),多方中使用many-to-one,一方使用<set><key/></one-to-many></set>,如果是單向的,則或者多方使用many-to-one,一方不用,或者一方使用<set><key/></one-to-many></set>,多方不用,由此可見,一對多關聯要根據是否是雙向有不同的實作,如果是雙向的顯然一個為one-to-many,一個為many-to-one,單向的則為任何一方維護即可,下面的這個例子是Photo一對多Pictures,這個地方的set屬于Photo的,顧這個地方設定了inverse=true,意思不維護這個關系,由Picture來維護,而cascade=all顧要進行級聯操作

<set name="pictures" inverse="true" cascade="all">              
     <key>  <column name="photosid" not-null="true" /> </key>             
     <one-to-many class="girl.domain.Picture" />        
 </set>
           

 many-to-one 和one-to-many關聯的例子

<class name=" com.test.hibernate.User" table="TBL_USER"> 
    <id name="id" column="userId"><generator class="native"/></id> 
    <many-to-one name=“group” column=“groupId” outer-join="false"/> 
</class> 
<class name=" com.test.hibernate .Group" table="TBL_GROUP"> 
     <id name="id" column="groupId"><generator class="native"/></id> 
</class> 
<class name="com.test.hibernate.User" table="TBL_USER"> 
    <id name="id" column="userId"><generator class="native"/></id> 
    <set name="addresses" lazy="true" cascade="all"> 
      <key column="addressId"/> 
      <one-to-many class="com.test.hibernate.Address"/> 
    </set> 
</class> 
<class name="com.test.hibernate.Address" table="TBL_ADDRESS"> 
    <id name="id" column="addressId"> <generator class="native"/></id> 
</class> 
           

多對多是使用中間表來實作的,兩個多方均用<set></many-to-many></set>實作 一個多對多關聯的例子

完整的配置檔案Student.hbm.xml如下所示。

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<hibernate-mapping>
      <class name="hibernate.ch06.Student" table="student" catalog="joblog">
           <id name="id" type="integer">
                 <column name="id" />
                 <generator class="identity"></generator>
           </id>
           <property name="sno" type="integer">     <!--映射學号-->
                 <column name="Sno" not-null="true" />
           </property>
           <property name="sname" type="string">           <!--映射姓名-->
                <column name="Sname" length="45" />
           </property>
           <property name="sdept" type="string">           <!--映射系部-->
                <column name="Sdept" length="10" />
           </property>
           <property name="sage" type="integer">         <!--映射年齡-->
                <column name="Sage" />
           </property>
           <property name="ssex" type="string">           <!--映射性别-->
                <column name="Ssex" length="2" />
           </property>
           <property name="saddress" type="string">           <!--映射住址-->
                <column name="Saddress" length="45" />
           </property>
           <set name="course" table="sc" lazy="false" cascade="save-update">          <!--映射課程表-->
               <key column="sno" />
               <many-to-many class="hibernate.ch06.Course" column="cno" />    <!--多對多-->
           </set>
       </class>
</hibernate-mapping>
           

緩存

Hibernate緩存分類:

一級緩存:Session緩存(又稱作事務緩存):Hibernate内置的,不能卸除。緩存範圍:緩存隻能被目前Session對象通路。緩存的生命周期依賴于Session的生命周期,當Session被關閉後,緩存也就結束生命周期。持久化對象經過save()方法會放到session緩存中,get和load如果是從資料庫中拿出的對象也會放在一級緩存,其他調用HQL和JDBC等從資料庫中查詢出來的資料也會放到session緩存中。

二級緩存:SessionFactory緩存(又稱作應用緩存):使用第三方插件,可插拔。緩存範圍:緩存被應用範圍内的所有session共享, 不同的Session可以共享。這些session有可能是并發通路緩存,是以必須對緩存進行更新。緩存的生命周期依賴于應用的生命周期,應用結束時,緩存也就結束了生命周期,二級緩存存在于應用程式範圍。

二級緩存實作原理:

 Hibernate如何将資料庫中的資料放入到二級緩存中?注意,你可以把緩存看做是一個Map對象,它的Key用于存儲對象OID,Value用于存儲POJO。首先,當我們使用Hibernate從資料庫中查詢出資料,擷取檢索的資料後,Hibernate将檢索出來的對象的OID放入緩存中key 中,然後将具體的POJO放入value中,等待下一次再次向資料查詢資料時,Hibernate根據你提供的OID先檢索一級緩存,若有且配置了二級緩存,則檢索二級緩存,如果還沒有則才向資料庫發送SQL語句,然後将查詢出來的對象放入緩存中。是以 Hibernate的二級緩存政策,是針對于ID查詢的緩存政策,對于條件查詢則毫無作用。為此,Hibernate提供了針對條件查詢的Query緩存。

針對OID的緩存:

  1) 條件查詢的時候,總是發出一條select * from table_name where …. (選擇所有字段)這樣的SQL語句查詢資料庫,一次獲得所有的資料對象。

  2) 把獲得的所有資料對象根據ID放入到第二級緩存中。

  3) 當Hibernate根據ID通路資料對象的時候,首先從Session一級緩存中查;查不到,如果配置了二級緩存,那麼從二級緩存中查;查不到,再查詢資料庫,把結果按照ID放入到緩存。

  4) 删除、更新、增加資料的時候,同時更新緩存。

針對條件查詢的Query緩存:

  Hibernate的Query緩存政策的過程如下:

  1) Hibernate首先根據這些資訊組成一個Query Key,Query Key包括條件查詢的請求一般資訊:SQL, SQL需要的參數,記錄範圍(起始位置rowStart,最大記錄個數maxRows),等。

  2) Hibernate根據這個Query Key到Query緩存中查找對應的結果清單。如果存在,那麼傳回這個結果清單;如果不存在,查詢資料庫,擷取結果清單,把整個結果清單根據Query Key放入到Query緩存中。

  3) Query Key中的SQL涉及到一些表名,如果這些表的任何資料發生修改、删除、增加等操作,這些相關的Query Key都要從緩存中清空。

資料從緩存中清除:

1. evit()将指定的持久化對象從緩存中清除,釋放對象所占用的記憶體資源,指定對象從持久化狀态變為脫管狀态,進而成為遊離對象。 

2. clear()将緩存中的所有持久化對象清除,釋放其占用的記憶體資源。

其他緩存操作:

1. contains()判斷指定的對象是否存在于緩存中。

2. flush()重新整理緩存區的内容,使之與資料庫資料保持同步。

隻有經正确的配置後二級緩存才會發揮作用。同時在進行條件查詢時必須使用相應的方法才能從緩存中擷取資料。比如Query.iterate()方法、load、get方法等。必須注意的是session.find方法永遠是從資料庫中擷取資料,不會從二級緩存中擷取資料,即便其中有其所需要的資料也是如此。

查詢時使用緩存的實作過程為:首先查詢一級緩存中是否具有需要的資料,如果沒有,查詢二級緩存,如果二級緩存中也沒有,此時再執行查詢資料庫的工作。要注意的是:此3種方式的查詢速度是依次降低的。即先一級後二級再資料庫,這是從範圍來考慮的。

Load預設使用二級緩存,就是當查一個對象的時候,它先會去二級緩存裡面去找,如果找到了就不去資料庫中查了。

Iterator預設的也會使用二級緩存,有的話就不去資料庫裡面查了,不發送select語句了。

List預設的往二級緩存中加資料,假如有一個Query,把資料拿出來之後會放到二級緩存,但是執行查詢的時候不會到二級緩存中查,會在資料庫中查。原因每個Query中查詢條件不一樣。

為了提高使用hibernate的性能,除了正常的一些需要注意的方法比如:

使用延遲加載、迫切外連接配接、查詢過濾等以外,還需要配置hibernate的二級緩存。對系統整體性能的改善往往具有立竿見影的效果!

隻要使用hibernate的API,hibernate就會自行維護二級緩存中的資料,以保證緩存中的資料和資料庫中的真實資料的一緻性!無論何時,當調用save()、update()或 saveOrUpdate()方法傳遞一個對象時,或使用load()、 get()、list()、iterate() 或scroll()方法獲得一個對象時, 該對象都将被加入到Session的内部緩存中。 當随後flush()方法被調用時,對象的狀态會和資料庫取得同步。

但還有幾種情況下不會繞開Hibernate導緻緩存出現不一緻性問題,不過使用緩存清除的方法可以手動保證資料的時效性,

其中二級緩存提供的清除方法為:(1)

按對象class清空緩存 (2)

按對象class和對象的主鍵id清空緩存 (3)

清空對象的集合中的緩存資料等。

先看一下适合使用二級緩存的情況:(1)資料不會被第三方修改,比如直接使用SQL語句進行修改,此種情況下要進行調用cache清除方法,進行手動保證資料時效性 (2)資料量不會造成記憶體的大量消耗,當資料量特别巨大的時候,要為其持久化對象單獨配置緩存政策,比如最大緩存數,緩存過期時間等 (3)資料更新頻率較低,更新頻率較大的資料顯然不适合 (4)非關鍵性對時效性要求特别嚴格的資料,比如财務資料。

在看一下哪些情況會導緻緩存失效:(1)多個應用系統同時通路一個資料庫,顯然的,因為二級緩存屬于sessionFactory,而多個sessionFactory同時對資料庫讀寫,顯然會造成資料時效性問題,可以使用資料庫的鎖機制進行避免 (2)運作時生成的動态表,沒有hibernate映射 (3)使用SQL語句對持久化對象進行批量删除,此時緩存中可能會存在被删的資料,此操作之後,不會對find(),和iterrate()方法造成影響,因為前者每次都會讀資料庫,後者第一次會去資料庫中擷取ID,傳回的都是最新的ID,本地即使有已被删的也不會被讀取。但get和load方法會存在問題,須進行清除緩存。

是以:

1、建議不要使用sql直接執行資料持久化對象的資料的更新,但是可以執行 批量删除。(系統中需要批量更新的地方也較少)

2、如果必須使用sql執行資料的更新,必須清空此對象的緩存資料。調用SessionFactory.evict(class)  SessionFactory.evict(class,id) 等方法。

3、在批量删除資料量不大的時候可以直接采用hibernate的批量删除,這樣就不存在繞開hibernate執行sql産生的緩存資料一緻性的問題。

4、不推薦采用hibernate的批量删除方法來删除大批量的記錄資料,即删除大量資料的時候使用SQl進行删除

原因是hibernate的批量删除會執行1條查詢語句外加 滿足條件的n條删除語句。而不是一次執行一條條件删除語句!!

當待删除的資料很多時會有很大的性能瓶頸!!!如果批量删除資料量較大,比如超過50條,可以采用JDBC直接删除。這樣作的好處是隻執行一條sql删除語句,性能會有很大的改善。同時,緩存資料同步的問題,可以采用 hibernate清除二級緩存中的相關資料的方法。

調用 SessionFactory.evict(class) ;SessionFactory.evict(class,id)等方法。

是以說,對于一般的應用系統開發而言(不涉及到叢集,分布式資料同步問題等),因為隻在中間關聯表執行批量删除時調用了sql執行,同時中間關聯表一般是執行條件查詢不太可能執行按id查詢。是以,此時可以直接執行sql删除,甚至不需要調用緩存的清除方法。這樣做不會導緻以後配置了二級緩存引起資料有效性的問題。

很多hibernate的使用者在調用其相應方法時都迷信的相信“hibernate會自行為我們處理性能的問題”,或者“hibernate會自動為我們的所有操作調用緩存”,實際的情況是hibernate雖然為我們提供了很好的緩存機制和擴充緩存架構的支援,但是必須經過正确的調用其才有可能發揮作用!!是以造成很多使用hibernate的系統的性能問題,實際上并不是hibernate不行或者不好,而是因為使用者沒有正确的了解其使用方法造成的。相反,如果配置得當hibernate的性能表現會讓你有相當“驚喜的”發現。下面我講解具體的配置方法.

Hibernate提供了二級緩存的接口: net.sf.hibernate.cache.Provider, 同時提供了一個預設的 實 net.sf.hibernate.cache.HashtableCacheProvider, 

二級緩存中常用的緩存政策:<cache usage="read-write"/>  read-only,read-write,transactional等

1 隻讀緩存 read only 

不須要鎖與事務,因為緩存自資料從資料庫加載後就不會改變。

    如果資料是隻讀的,例如引用資料,那麼總是使用“read-only”政策,因為它是最簡單、最高效的政策,也是叢集安全的政策。是性能第一的政策 。

2 讀寫緩存 read write 

對緩存的更新發生在資料庫事務完成後。緩存需要支援鎖。

在一個事務中更新資料庫,在這個事務成功完成後更新緩存,并釋放鎖。 

鎖隻是一種特定的緩存值失效表述方式,在它獲得新資料庫值前阻止其他事務讀寫緩存。那些事務會轉而直接讀取資料庫。

緩存必須支援鎖,事務支援則不是必須的。如果緩存是一個叢集,“更新緩存”的調用會将新值推送給所有副本,這通常被稱為“推(push)”更新政策。

    如果你的資料是又讀又寫的,那麼使用“read-write”政策。這通常是性能第三的政策,因為它要求有緩存鎖,緩存叢集中使用重量級的“推”更新政策。

二級緩存方法提供商:

提供了HashTable緩存,EHCache,OSCache,SwarmCache,jBoss Cathe2,這些緩存機制,其中EHCache,OSCache是不能用于叢集環境(Cluster Safe)的,而SwarmCache,jBoss Cathe2是可以的。HashTable緩存主要是用來測試的,隻能把對象放在記憶體中,EHCache,OSCache可以把對象放在記憶體(memory)中,也可以把對象放在硬碟(disk)上

使用EhCache來進行配置二級緩存執行個體:

在本Blog中的一偏Spring和Hibernate融合的XML配置中也有關于緩存的配置執行個體:http://blog.csdn.net/luoshenfu001/article/details/5816425  

(1)在hibernate.cfg.xml中,打開二級緩存:

<!--使用二級緩存 - ->
 <property name="cache.use_second_level_cache">true</property>
 <!--設定緩存的類型,設定緩存的提供商-->
 <property  name="cache.provider_class">org.hibernate.cache.EhCacheProvider</property>
           

(2)配置 ehcache.xml

<ehcache>
    <!-- 緩存到硬碟的路徑	-->
    <diskStore path="d:/ehcache"/>
    <defaultCache
        maxElementsInMemory="200"<!-- 最多緩存多少個對象 -->
        eternal="false"<!-- 記憶體中的對象是否永遠不變 -->
        timeToIdleSeconds="50"<!--發呆了多長時間,沒有人通路它,這麼長時間清除 -->
        timeToLiveSeconds="60"<!--活了多長時間,活了1200秒後就可以拿走,一般Live要比Idle設定的時間長 -->
        overflowToDisk="true"<!--記憶體中溢出就放到硬碟上 -->
        />
    <!--指定緩存的對象,緩存哪一個實體類,下面出現的的屬性覆寫上面出現的,沒出現的繼承上面-->
    <cache name="com.suxiaolei.hibernate.pojos.Order"
        maxElementsInMemory="200"
        eternal="true"
        timeToIdleSeconds="0"
        timeToLiveSeconds="0"
        overflowToDisk="false"
        /> 
</ehcache>
           

(3)使用二級緩存需要在實體類中加入注解:需要ehcache-1.2.jar和commons_loging1.1.1.jar包

@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)

(4)也可以在需要被緩存的對象中hbm檔案中的<class>标簽下添加一個<cache>子标簽:

<hibernate-mapping>
        <class name="com.suxiaolei.hibernate.pojos.Order" table="orders">
            <cache usage="read-only"/>
            <id name="id" type="string">
                <column name="id"></column>
                <generator class="uuid"></generator>
            </id>
           
            <property name="orderNumber" column="orderNumber" type="string"></property>
            <property name="cost" column="cost" type="integer"></property>
           
            <many-to-one name="customer" class="com.suxiaolei.hibernate.pojos.Customer" column="customer_id" cascade="save-update">
            </many-to-one>       
        </class>
    </hibernate-mapping>
           

存在一對多的關系,想要在在擷取一方的時候将關聯的多方緩存起來,需要在集合屬性下添加<cache>子标簽,這裡需要将關聯的對象的hbm檔案中必須在存在<class>标簽下也添加<cache>标簽,不然Hibernate隻會緩存OID。

<hibernate-mapping>
        <class name="com.suxiaolei.hibernate.pojos.Customer" table="customer">
            <!-- 主鍵設定 -->
            <id name="id" type="string">
                <column name="id"></column>
                <generator class="uuid"></generator>
            </id>
           
            <!-- 屬性設定 -->
            <property name="username" column="username" type="string"></property>
            <property name="balance" column="balance" type="integer"></property>
            <set name="orders" inverse="true" cascade="all" lazy="false" fetch="join">
                <cache usage="read-only"/>
                <key column="customer_id" ></key>
                <one-to-many class="com.suxiaolei.hibernate.pojos.Order"/>
            </set>
        </class>
    </hibernate-mapping>
           

關于Hiberanate緩存的原理 http://myoraclex.blog.51cto.com/2288027/413183

hibernate緩存管理 http://www.blogjava.net/stevenjohn/archive/2012/03/14/371893.html

hibernate緩存使用 http://blog.csdn.net/woshichenxu/article/details/586361

在本Blog中的一偏Spring和Hibernate融合的XML配置中也有關于緩存的配置執行個體: http://blog.csdn.net/luoshenfu001/article/details/5816425  

繼續閱讀