熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Java編程 >> Java核心技術 >> 正文

hibernate大數據性能處理

2013-11-23 18:58:30  來源: Java核心技術 
    在項目中使用Hibernate進行大數據量的性能測試有一些總結
    ) 在處理大數據量時會有大量的數據緩沖保存在Session的一級緩存中這緩存大太時會嚴重顯示性能所以在使用Hibernate處理大數據量的可以使用sessionclear()或者session Evict(Object) 在處理過程中清除全部的緩存或者清除某個對象
    ) 對大數據量查詢時慎用list()或者iterator()返回查詢結果
    使用List()返回結果時Hibernate會所有查詢結果初始化為持久化對象結果集較大時會占用很多的處理時間
    而使用iterator()返回結果時在每次調用iteratornext()返回對象並使用對象時Hibernate才調用查詢將對應的對象初始化對於大數據量時每調用一次查詢都會花費較多的時間當結果集較大但是含有較大量相同的數據或者結果集不是全部都會使用時使用iterator()才有優勢
    對於大數據量使用qryscroll()可以得到較好的處理速度以及性能而且直接對結果集向前向後滾動
    ) 對於關聯操作Hibernate雖然可以表達復雜的數據關系但請慎用使數據關系較為簡單時會得到較好的效率特別是較深層次的關聯時性能會很差
    ) 對含有關聯的PO(持久化對象)時若defaultcascade=all或者 saveupdate新增PO時請注意對PO中的集合的賦值操作因為有可能使得多執行一次update操作
    ) 在一對多多對一的關系中使用延遲加載機制會使不少的對象在使用時方會初始化這樣可使得節省內存空間以及減少的負荷而且若PO中的集合沒有被使用時就可減少互數據庫的交互從而減少處理時間  數據庫
    什麼叫n+次select查詢問題?
    在Session的緩存中存放的是相互關聯的對象圖默認情況下當Hibernate從數據庫中加載Customer對象時會同時加載所有關聯的Order對象以Customer和Order類為例假定ORDERS表的CUSTOMER_ID外鍵允許為null列出了CUSTOMERS表和ORDERS表中的記錄
    以下Session的find()方法用於到數據庫中檢索所有的Customer對象
    List customerLists=sessionfind(from Customer as c
    運行以上find()方法時Hibernate將先查詢CUSTOMERS表中所有的記錄然後根據每條記錄的ID到ORDERS表中查詢有參照關系的記錄Hibernate將依次執行以下select語句
    select * from CUSTOMERS;
    select * from ORDERS where CUSTOMER_ID=;
    select * from ORDERS where CUSTOMER_ID=;
    select * from ORDERS where CUSTOMER_ID=;
    select * from ORDERS where CUSTOMER_ID=;
    通過以上條select語句Hibernate最後加載了個Customer對象和個Order對象在內存中形成了一幅關聯的對象圖參見圖
    Hibernate在檢索與Customer關聯的Order對象時使用了默認的立即檢索策略這種檢索策略存在兩大不足
    (a) select語句的數目太多需要頻繁的訪問數據庫會影響檢索性能如果需要查詢n個Customer對象那麼必須執行n+次select查詢語句這就是經典的n+次select查詢問題這種檢索策略沒有利用SQL的連接查詢功能例如以上條select語句完全可以通過以下條select語句來完成
    select * from CUSTOMERS left outer join ORDERS
    on CUSTOMERSID=ORDERSCUSTOMER_ID
    以上select語句使用了SQL的左外連接查詢功能能夠在一條select語句中查詢出CUSTOMERS表的所有記錄以及匹配的ORDERS表的記錄
    (b)在應用邏輯只需要訪問Customer對象而不需要訪問Order對象的場合加載Order對象完全是多余的操作這些多余的Order對象白白浪費了許多內存空間
    為了解決以上問題Hibernate提供了其他兩種檢索策略延遲檢索策略和迫切左外連接檢索策略延遲檢索策略能避免多余加載應用程序不需要訪問的關聯對象迫切左外連接檢索策略則充分利用了SQL的外連接查詢功能能夠減少select語句的數目
    剛查閱了hibernate的文檔
    查詢抓取(默認的)在N+查詢的情況下是極其脆弱的因此我們可能會要求在映射文檔中定義使用連接抓取
    <set name=permissions
    fetch=join>
    <key column=userId/>
    <onetomany class=Permission/>
    </set
    <manytoone name=mother class=Cat fetch=join/>
    在映射文檔中定義的抓取策略將會有產生以下影響
    通過get()或load()方法取得數據
    只有在關聯之間進行導航時才會隱式的取得數據(延遲抓取)
    條件查詢
    在映射文檔中顯式的聲明 連接抓取做為抓取策略並不會影響到隨後的HQL查詢
    通常情況下我們並不使用映射文檔進行抓取策略的定制更多的是保持其默認值然後在特定的事務中 使用HQL的左連接抓取(left join fetch) 對其進行重載這將通知 Hibernate在第一次查詢中使用外部關聯(outer join)直接得到其關聯數據 在條件查詢 API中應該調用 setFetchMode(FetchModeJOIN)語句
    ) 對於大數據量新增修改刪除操作或者是對大數據量的查詢與數據庫的交互次數是決定處理時間的最重要因素減少交互的次數是提升效率的最好途徑所以在開發過程中請將show_sql設置為true深入了解Hibernate的處理過程嘗試不同的方式可以使得效率提升
    ) Hibernate是以JDBC為基礎但是Hibernate是對JDBC的優化其中使用Hibernate的緩沖機制會使性能提升如使用二級緩存以及查詢緩存若命中率較高明性能會是到大幅提升
    ) Hibernate可以通過設置hibernatejdbcfetch_sizehibernatejdbcbatch_size等屬性對Hibernate進行優化
    hibernatejdbcfetch_size
    hibernatejdbcbatch_size
    這兩個選項非常非常非常重要!!!將嚴重影響Hibernate的CRUD性能!
    C = create R = read U = update D = delete
    Fetch Size 是設定JDBC的Statement讀取數據的時候每次從數據庫中取出的記錄條數


    例如一次查詢萬條記錄對於Oracle的JDBC驅動來說是不會次性把萬條取出來的而只會取出Fetch Size條數當紀錄集遍歷完了這些記錄以後再去數據庫取Fetch Size條數據
    因此大大節省了無謂的內存消耗當然Fetch Size設的越大讀數據庫的次數越少速度越快Fetch Size越小讀數據庫的次數越多速度越慢
    這有點像平時我們寫程序寫硬盤文件一樣設立一個Buffer每次寫入Buffer等Buffer滿了以後一次寫入硬盤道理相同
    Oracle數據庫的JDBC驅動默認的Fetch Size=是一個非常保守的設定根據我的測試當Fetch Size=的時候性能會提升倍之多當Fetch Size=性能還能繼續提升%Fetch Size繼續增大性能提升的就不顯著了
    因此我建議使用Oracle的一定要將Fetch Size設到
    不過並不是所有的數據庫都支持Fetch Size特性例如MySQL就不支持
    MySQL就像我上面說的那種最壞的情況他總是一下就把萬條記錄完全取出來內存消耗會非常非常驚人!這個情況就沒有什麼好辦法了 :(
    Batch Size是設定對數據庫進行批量刪除批量更新和批量插入的時候的批次大小有點相當於設置Buffer緩沖區大小的意思
    Batch Size越大批量操作的向數據庫發送sql的次數越少速度就越快我做的一個測試結果是當Batch Size=的時候使用Hibernate對Oracle數據庫刪除萬條記錄需要Batch Size = 的時候刪除僅僅需要秒!!!
    //
    我們通常不會直接操作一個對象的標識符(identifier)因此標識符的setter方法應該被聲明為私有的(private)這樣當一個對象被保存的時候只有Hibernate可以為它分配標識符你會發現Hibernate可以直接訪問被聲明為publicprivate和protected等不同級別訪問控制的方法(accessor method)和字段(field) 所以選擇哪種方式來訪問屬性是完全取決於你你可以使你的選擇與你的程序設計相吻合
    所有的持久類(persistent classes)都要求有無參的構造器(noargument constructor)因為Hibernate必須要使用Java反射機制(Reflection)來實例化對象構造器(constructor)的訪問控制可以是私有的(private)然而當生成運行時代理(runtime proxy)的時候將要求使用至少是package級別的訪問控制這樣在沒有字節碼編入(bytecode instrumentation)的情況下從持久化類裡獲取數據會更有效率一些
    而
    hibernatemax_fetch_depth 設置外連接抓取樹的最大深度
    取值 建議設置為之間
    就是每次你在查詢時會級聯查詢的深度譬如你對關聯vo設置了eager的話如果fetch_depth值太小的話會發多很多條sql
    Hibernate的Reference之後可以采用批量處理的方法當插入的數據超過就flush session並且clear
    下面是一個測試method
       /** */ /**
          * 測試成批插入數據的事務處理返回是否成功
          *
          *  @param objPO Object
          *  @return boolean
      */
       public boolean  insertBatch( final  Object objPO)   {
      boolean  isSuccess  = false ;
             Transaction transaction  = null ;
             Session session  = openSession()
       try  {
                 transaction  = sessionbeginTransaction()
       for  ( int  i  = ; i  < ; i ++ )   {
                     sessionsave(objPO)
       if  (i  % == )   {
      //  flush a batch of inserts and release memory
                          sessionflush()
                         sessionclear()
                     }
                 }
                 mit()
                 ( transactionwasCommitted:
      + transactionwasCommitted())
                 isSuccess  = true ;
              } catch  (HibernateException ex)   {
       if  (transaction  != null )   {
       try  {
                         transactionrollback()
                         loggererror( transactionwasRolledBack:
      + transactionwasRolledBack())
                      } catch  (HibernateException ex)   {
                         loggererror(exgetMessage())
                         exprintStackTrace()
                     }
                 }
                 loggererror( Insert Batch PO Error: + exgetMessage())
                 exprintStackTrace()
              } finally  {
       if  (transaction  != null )   {
                     transaction  = null ;
                 }
                 sessionclose()
             }
      return isSuccess;
         }
   
    這只是簡單的測試實際項目中遇到的問題要比這個復雜得多
    這時候我們可以讓Spring來控制Transaction自己來控制Hibernate的Session隨時更新數據
    首先利用HibernateDaoSupport類來自定義個方法打開Session
    public Session openSession(){
   
    return getHibernateTemplate()getSessionFactory()openSession()
   
        }
    然後用打開的Session處理你的數據
    protected void doBusiness(Session session) {
   
    while (true) {
    //do your business with the opening session
                someMethod(session)
                sessionflush()
                sessionclear()
                (good job!
            }
    }
    每做一次數據操作就更新一次Session這樣可以保證每次數據操作都成功否則就讓Spring去控制它roll back吧
    最後記得關閉Session
      Session session  =  openSession()
    doBusiness(session)
    sessionclose()  // 關閉session


From:http://tw.wingwit.com/Article/program/Java/hx/201311/26054.html
    Copyright © 2005-2013 電腦知識網 Computer Knowledge   All rights reserved.