hibernate批量處理大數據

spring在管理hibernate上有獨到的地方能夠順手拿來用,我也是想在能不拋棄hibernate的基礎上儘量多挖掘一下它的一些性能提高上的作法,總結你們的見解,基本得出一致結論:複雜查詢依靠jdbc的sql或者hibernate提供的本地化sql封裝,或者使用spring的管理,均可以提高性能和效率.我認爲對於hibernate的性能優化應該是無止境的.spring

在項目中使用Hibernate進行大數據量的性能測試,有一些總結,  
        1) 在處理大數據量時,會有大量的數據緩衝保存在Session的一級緩存中,這緩存大太時會嚴重顯示性能,因此在使用Hibernate處理大數據量的,可使用session.clear()或者session. Evict(Object) 在處理過程當中,清除所有的緩存或者清除某個對象。  
        2) 對大數據量查詢時,慎用list()或者iterator()返回查詢結果,  
        1. 使用List()返回結果時,Hibernate會全部查詢結果初始化爲持久化對象,結果集較大時,會佔用不少的處理時間。  
        2. 而使用iterator()返回結果時,在每次調用iterator.next()返回對象並使用對象時,Hibernate才調用查詢將對應的對象初始化,對於大數據量時,每調用一次查詢都會花費較多的時間。當結果集較大,可是含有較大量相同的數據,或者結果集不是所有都會使用時,使用iterator()纔有優點。  
        3. 對於大數據量,使用qry.scroll()能夠獲得較好的處理速度以及性能。並且直接對結果集向前向後滾動。  
        3) 對於關聯操做,Hibernate雖然能夠表達複雜的數據關係,但請慎用,使數據關係較爲簡單時會獲得較好的效率,特別是較深層次的關聯時,性能會不好。  
        4) 對含有關聯的PO(持久化對象)時,若default-cascade="all"或者 「save-update」,新增PO時,請注意對PO中的集合的賦值操做,由於有可能使得多執行一次update操做。  
        5) 在一對多、多對一的關係中,使用延遲加載機制,會使很多的對象在使用時方會初始化,這樣可以使得節省內存空間以及減小的負荷,並且若PO中的集合沒有被使用時,就可減小互數據庫的交互從而減小處理時間。  數據庫什麼叫n+1次select查詢問題?
在Session的緩存中存放的是相互關聯的對象圖。默認狀況下,當Hibernate從數據庫中加載Customer對象時,會同時加載全部關聯的Order對象。以Customer和Order類爲例,假定ORDERS表的CUSTOMER_ID外鍵容許爲null,圖1列出了CUSTOMERS表和ORDERS表中的記錄。
如下Session的find()方法用於到數據庫中檢索全部的Customer對象:
List customerLists=session.find("from Customer as c");
運行以上find()方法時,Hibernate將先查詢CUSTOMERS表中全部的記錄,而後根據每條記錄的ID,到ORDERS表中查詢有參照關係的記錄,Hibernate將依次執行如下select語句:sql

select * from CUSTOMERS; 
select * from ORDERS where CUSTOMER_ID=1;
select * from ORDERS where CUSTOMER_ID=2;
select * from ORDERS where CUSTOMER_ID=3;
select * from ORDERS where CUSTOMER_ID=4;
View Code

經過以上5條select語句,Hibernate最後加載了4個Customer對象和5個Order對象,在內存中造成了一幅關聯的對象圖,參見圖2。數據庫

Hibernate在檢索與Customer關聯的Order對象時,使用了默認的當即檢索策略。這種檢索策略存在兩大不足:
(a) select語句的數目太多,須要頻繁的訪問數據庫,會影響檢索性能。若是須要查詢n個Customer對象,那麼必須執行n+1次select查詢語句。這就是經典的n+1次select查詢問題。這種檢索策略沒有利用SQL的鏈接查詢功能,例如以上5條select語句徹底能夠經過如下1條select語句來完成:緩存

select * from CUSTOMERS left outer join ORDERS 
on CUSTOMERS.ID=ORDERS.CUSTOMER_ID 

 



以上select語句使用了SQL的左外鏈接查詢功能,可以在一條select語句中查詢出CUSTOMERS表的全部記錄,以及匹配的ORDERS表的記錄。

(b)在應用邏輯只須要訪問Customer對象,而不須要訪問Order對象的場合,加載Order對象徹底是多餘的操做,這些多餘的Order對象白白浪費了許多內存空間。
爲了解決以上問題,Hibernate提供了其餘兩種檢索策略:延遲檢索策略和迫切左外鏈接檢索策略。延遲檢索策略能避免多餘加載應用程序不須要訪問的關聯對象,迫切左外鏈接檢索策略則充分利用了SQL的外鏈接查詢功能,可以減小select語句的數目。

剛查閱了hibernate3的文檔: 
查詢抓取(默認的)在N+1查詢的狀況下是極其脆弱的,所以咱們可能會要求在映射文檔中定義使用鏈接抓取: 性能優化

<set name="permissions" 
            fetch="join"> 
    <key column="userId"/> 
    <one-to-many class="Permission"/> 
</set 
<many-to-one name="mother" class="Cat" fetch="join"/> 

在映射文檔中定義的抓取策略將會有產生如下影響: session

經過get()或load()方法取得數據。 
只有在關聯之間進行導航時,纔會隱式的取得數據(延遲抓取)。 
條件查詢 
在映射文檔中顯式的聲明 鏈接抓取作爲抓取策略並不會影響到隨後的HQL查詢。 
一般狀況下,咱們並不使用映射文檔進行抓取策略的定製。更多的是,保持其默認值,而後在特定的事務中, 使用HQL的左鏈接抓取(left join fetch) 對其進行重載。這將通知 Hibernate在第一次查詢中使用外部關聯(outer join),直接獲得其關聯數據。 在條件查詢 API中,應該調用 setFetchMode(FetchMode.JOIN)語句。
        6) 對於大數據量新增、修改、刪除操做或者是對大數據量的查詢,與數據庫的交互次數是決定處理時間的最重要因素,減小交互的次數是提高效率的最好途徑,因此在開發過程當中,請將show_sql設置爲true,深刻了解Hibernate的處理過程,嘗試不一樣的方式,可使得效率提高。  
        7) Hibernate是以JDBC爲基礎,可是Hibernate是對JDBC的優化,其中使用Hibernate的緩衝機制會使性能提高,如使用二級緩存以及查詢緩存,若命中率較高明,性能會是到大幅提高。  
        8) Hibernate能夠經過設置hibernate.jdbc.fetch_size,hibernate.jdbc.batch_size等屬性,對Hibernate進行優化。
      hibernate.jdbc.fetch_size 50
      hibernate.jdbc.batch_size 25
      這兩個選項很是很是很是重要!!!將嚴重影響Hibernate的CRUD性能!
       C = create, R = read, U = update, D = delete
      Fetch Size 是設定JDBC的Statement讀取數據的時候每次從數據庫中取出的記錄條數。
    例如一次查詢1萬條記錄,對於Oracle的JDBC驅動來講,是不會1次性把1萬條取出來的,而只會取出Fetch Size條數,當紀錄集遍歷完了這些記錄之後,再去數據庫取Fetch Size條數據。
所以大大節省了無謂的內存消耗。固然Fetch Size設的越大,讀數據庫的次數越少,速度越快;Fetch Size越小,讀數據庫的次數越多,速度越慢。
這有點像平時咱們寫程序寫硬盤文件同樣,設立一個Buffer,每次寫入Buffer,等Buffer滿了之後,一次寫入硬盤,道理相同。
Oracle數據庫的JDBC驅動默認的Fetch Size=10,是一個很是保守的設定,根據個人測試,當Fetch Size=50的時候,性能會提高1倍之多,當Fetch Size=100,性能還能繼續提高20%,Fetch Size繼續增大,性能提高的就不顯著了。
所以我建議使用Oracle的必定要將Fetch Size設到50。
不過並非全部的數據庫都支持Fetch Size特性,例如MySQL就不支持。
MySQL就像我上面說的那種最壞的狀況,他老是一下就把1萬條記錄徹底取出來,內存消耗會很是很是驚人!這個狀況就沒有什麼好辦法了 :(
Batch Size是設定對數據庫進行批量刪除,批量更新和批量插入的時候的批次大小,有點至關於設置Buffer緩衝區大小的意思。
Batch Size越大,批量操做的向數據庫發送sql的次數越少,速度就越快。我作的一個測試結果是當Batch Size=0的時候,使用Hibernate對Oracle數據庫刪除1萬條記錄須要25秒,Batch Size = 50的時候,刪除僅僅須要5秒!!!
//咱們一般不會直接操做一個對象的標識符(identifier),所以標識符的setter方法應該被聲明爲私有的(private)。這樣當一個對象被保存的時候,只有Hibernate能夠爲它分配標識符。你會發現Hibernate能夠直接訪問被聲明爲public,private和protected等不一樣級別訪問控制的方法(accessor method)和字段(field)。 因此選擇哪一種方式來訪問屬性是徹底取決於你,你可使你的選擇與你的程序設計相吻合。
全部的持久類(persistent classes)都要求有無參的構造器(no-argument constructor);由於Hibernate必需要使用Java反射機制(Reflection)來實例化對象。構造器(constructor)的訪問控制能夠是私有的(private),然而當生成運行時代理(runtime proxy)的時候將要求使用至少是package級別的訪問控制,這樣在沒有字節碼編入(bytecode instrumentation)的狀況下,從持久化類裏獲取數據會更有效率一些。
而hibernate.max_fetch_depth 設置外鏈接抓取樹的最大深度 
取值. 建議設置爲0到3之間
 就是每次你在查詢時,會級聯查詢的深度,譬如你對關聯vo設置了eager的話,若是fetch_depth值過小的話,會發多不少條sql
Hibernate的Reference以後,能夠採用批量處理的方法,當插入的數據超過10000時,就flush session而且clear。 
下面是一個測試method。
  View Codeide

這只是簡單的測試,實際項目中遇到的問題,要比這個複雜得多。 性能

這時候,咱們可讓Spring來控制Transaction,本身來控制Hibernate的Session,隨時更新數據。 
首先,利用HibernateDaoSupport類來自定義個方法打開Session;
  View Code測試

而後,用打開的Session處理你的數據;fetch

  View Code

每作一次數據操做,就更新一次Session,這樣能夠保證每次數據操做都成功,不然就讓Spring去控制它roll back吧。 

最後,記得關閉Session。1  Session session  =  openSession(); 2 doBusiness(session); 3 session.close();  // 關閉session

相關文章
相關標籤/搜索