在項目中使用Hibernate進行大數據量的性能測試
有一些總結
) 在處理大數據量時
會有大量的數據緩沖保存在Session的一級緩存中
這緩存大太時會嚴重顯示性能
所以在使用Hibernate處理大數據量的
可以使用session
clear()或者session
Evict(Object) 在處理過程中
清除全部的緩存或者清除某個對象
) 對大數據量查詢時
慎用list()或者iterator()返回查詢結果
使用List()返回結果時
Hibernate會所有查詢結果初始化為持久化對象
結果集較大時
會占用很多的處理時間
而使用iterator()返回結果時
在每次調用iterator
next()返回對象並使用對象時
Hibernate才調用查詢將對應的對象初始化
對於大數據量時
每調用一次查詢都會花費較多的時間
當結果集較大
但是含有較大量相同的數據
或者結果集不是全部都會使用時
使用iterator()才有優勢
對於大數據量
使用qry
scroll()可以得到較好的處理速度以及性能
而且直接對結果集向前向後滾動
) 對於關聯操作
Hibernate雖然可以表達復雜的數據關系
但請慎用
使數據關系較為簡單時會得到較好的效率
特別是較深層次的關聯時
性能會很差
) 對含有關聯的PO(持久化對象)時
若default
cascade=
all
或者
save
update
新增PO時
請注意對PO中的集合的賦值操作
因為有可能使得多執行一次update操作
) 在一對多
多對一的關系中
使用延遲加載機制
會使不少的對象在使用時方會初始化
這樣可使得節省內存空間以及減少的負荷
而且若PO中的集合沒有被使用時
就可減少互數據庫的交互從而減少處理時間
數據庫
什麼叫n+
次select查詢問題?
在Session的緩存中存放的是相互關聯的對象圖
默認情況下
當Hibernate從數據庫中加載Customer對象時
會同時加載所有關聯的Order對象
以Customer和Order類為例
假定ORDERS表的CUSTOMER_ID外鍵允許為null
圖
列出了CUSTOMERS表和ORDERS表中的記錄
以下Session的find()方法用於到數據庫中檢索所有的Customer對象
List customerLists=session
find(
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 CUSTOMERS
ID=ORDERS
CUSTOMER_ID
以上select語句使用了SQL的左外連接查詢功能
能夠在一條select語句中查詢出CUSTOMERS表的所有記錄
以及匹配的ORDERS表的記錄
(b)在應用邏輯只需要訪問Customer對象
而不需要訪問Order對象的場合
加載Order對象完全是多余的操作
這些多余的Order對象白白浪費了許多內存空間
為了解決以上問題
Hibernate提供了其他兩種檢索策略
延遲檢索策略和迫切左外連接檢索策略
延遲檢索策略能避免多余加載應用程序不需要訪問的關聯對象
迫切左外連接檢索策略則充分利用了SQL的外連接查詢功能
能夠減少select語句的數目
剛查閱了hibernate
的文檔
查詢抓取(默認的)在N+
查詢的情況下是極其脆弱的
因此我們可能會要求在映射文檔中定義使用連接抓取
<set name=
permissions
fetch=
join
>
<key column=
userId
/>
<one
to
many class=
Permission
/>
</set
<many
to
one name=
mother
class=
Cat
fetch=
join
/>
在映射文檔中定義的抓取策略將會有產生以下影響
通過get()或load()方法取得數據
只有在關聯之間進行導航時
才會隱式的取得數據(延遲抓取)
條件查詢
在映射文檔中顯式的聲明 連接抓取做為抓取策略並不會影響到隨後的HQL查詢
通常情況下
我們並不使用映射文檔進行抓取策略的定制
更多的是
保持其默認值
然後在特定的事務中
使用HQL的左連接抓取(left join fetch) 對其進行重載
這將通知 Hibernate在第一次查詢中使用外部關聯(outer join)
直接得到其關聯數據
在條件查詢 API中
應該調用 setFetchMode(FetchMode
JOIN)語句
) 對於大數據量新增
修改
刪除操作或者是對大數據量的查詢
與數據庫的交互次數是決定處理時間的最重要因素
減少交互的次數是提升效率的最好途徑
所以在開發過程中
請將show_sql設置為true
深入了解Hibernate的處理過程
嘗試不同的方式
可以使得效率提升
) Hibernate是以JDBC為基礎
但是Hibernate是對JDBC的優化
其中使用Hibernate的緩沖機制會使性能提升
如使用二級緩存以及查詢緩存
若命中率較高明
性能會是到大幅提升
) Hibernate可以通過設置hibernate
jdbc
fetch_size
hibernate
jdbc
batch_size等屬性
對Hibernate進行優化
hibernate
jdbc
fetch_size
hibernate
jdbc
batch_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可以直接訪問被聲明為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 設置外連接抓取樹的最大深度
取值
建議設置為
到
之間
就是每次你在查詢時
會級聯查詢的深度
譬如你對關聯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 = session
beginTransaction()
for ( int i =
; i <
; i ++ ) {
session
save(objPO)
if (i %
==
) {
// flush a batch of inserts and release memory
session
flush()
session
clear()
}
}
mit()
(
transaction
wasCommitted:
+ transaction
wasCommitted())
isSuccess = true ;
} catch (HibernateException ex) {
if (transaction != null ) {
try {
transaction
rollback()
logger
error(
transaction
wasRolledBack:
+ transaction
wasRolledBack())
} catch (HibernateException ex
) {
logger
error(ex
getMessage())
ex
printStackTrace()
}
}
logger
error(
Insert Batch PO Error:
+ ex
getMessage())
ex
printStackTrace()
} finally {
if (transaction != null ) {
transaction = null ;
}
session
close()
}
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)
session
flush()
session
clear()
(
good job!
)
}
}
每做一次數據操作
就更新一次Session
這樣可以保證每次數據操作都成功
否則就讓Spring去控制它roll back吧
最後
記得關閉Session
Session session = openSession()
doBusiness(session)
session
close()
// 關閉session
From:http://tw.wingwit.com/Article/program/Java/hx/201311/26054.html