1. 程式人生 > >Hibernate效能優化【轉】

Hibernate效能優化【轉】

有很多人認為Hibernate天生效率比較低,確實,在普遍情況下,需要將執行轉換為SQL語句的Hibernate的效率低於直接JDBC存取,然而,在經過比較好的效能優化之後,Hibernate的效能還是讓人相當滿意的,特別是應用二級快取之後,甚至可以獲得比較不使用快取的JDBC更好的效能,下面介紹一些通常的Hibernate效能優化的策略:

[b]1.Hibernate效能優化之抓取優化[/b]
抓取是指Hibernate如何在關聯關係之間進行導航的時候,Hibernate如何獲取關聯物件的策略,其主要定義了兩個方面:如何抓取和何時抓取

1)如何抓取。
Hibernate3主要有兩種種抓取方式,分.應用於物件關聯例項(many-to-one、one-to-one)和物件關聯集合(set、map等),總共是四種變種
JOIN抓取: 通過在SELECT語句中使用OUTER JOIN來獲得物件的關聯例項或者關聯集合)
SELECT抓取: 另外發送一條SELECT語句來抓取當前物件的關聯實體和集合
在我的開發經歷中,此處對效能的優化是比較有限的,並不值得過多關注
例:
A.應用於物件關聯例項(預設是false)
".." outer-join="true/false/auto"  .../>  

B.應用於物件關聯集合(預設是auto)

".." fetch="join/select" ... >
....

2)何時抓取
主要分為延遲載入和立即抓取,預設的情況下Hibernate3對物件關聯實採用延遲載入,普通屬性採用立即抓取,通過延遲載入和採用適當的抓取粒度,與不採用優化相比往往可以將效能提升數倍
立即抓取:當抓取宿主物件時,同時抓取其關聯物件和關聯集以及屬性
延遲載入:當抓取宿主物件時,並不抓取其關聯物件,而是當對其物件進行呼叫時才載入
例:
A.應用於物件關聯例項(預設是延遲載入)

B.應用於物件關聯集合(預設是延遲載入)

對於延遲載入,需要注意的時,對延遲物件的使用必須在Session關閉之前進行,Hibernate的LazyInitalizationException往往就是由於在Session的生命期外使用了延遲載入的物件。當我們進行Web開發時,可以使用OpenSessionInView模式,當請求開始時開啟session,當請求響應結束時才關閉session,不過,在使用OpenSessionInView模式時,需要注意如果響應時間比較長(業務比較複雜或者客戶端是低速網路),將Session資源(也就是資料庫的連線)佔用太久的話可以會導致資源耗盡

3)抓取粒度
抓取粒度指的是物件在關聯關係之間被導航時一次預先載入的數量,Hibernate程式的效能比較差往往就在於沒有對抓取粒度仔細考慮,當載入一個列表並在列表中的每個物件中對其關聯進行導航時,往往導致N+1條SQL語句查詢。
例:
A.應用於物件關聯例項(預設為1),注意,對物件關聯例項的設定是在被關聯的物件之上的,譬如

class User
{
Group g;
}

那麼抓取粒度應該在Group的配置檔案之上,見下

<class name="Group" table="group" batch-size="..">
...
class>

對該值並沒有一個約定俗成的值,根據情況而定,如果被關聯表資料比較少,則可以設定地小一些,3-20,如果比較大則可以設到30-50,注意的時候,並不是越多越好,當其值超過50之後,對效能並沒有多大改善但卻無謂地消耗記憶體
假設有如下例子:
List users = query.list();
如果有20個User,並對這20個User及其Group進行遍歷,如果不設定batch-size(即batch-size="1"),則在最糟糕的情況下,需要1 + 20條SQL語句,如果設定batch-size="10",則最好的情況下只需要1 + 2條SQL語句
B.應用於物件關聯集合(預設為1)

".." batch-size="" ... >
....


[b]2.Hibernate效能優化之二級快取優化[/b]
Hibernate對資料的快取包括兩個級:一級快取,在Session的級別上進行,主要是物件快取,以其id為鍵儲存物件,在Session的生命期間存在;二級快取,在SessionFactory的級別上進行,有物件快取和查詢快取,查詢快取以查詢條件為鍵儲存查詢結果,在SessionFactory的生命期間存在。預設地,Hibernate只啟用一級快取,通過正確地使用二級快取,往往可以獲得意想不到的效能。
1)物件快取:
當抓取一個物件之後,Hiberate將其以id為鍵快取起來,當下次碰到抓取id相同的物件時,可以使用如下配置
方法1:在快取物件上配置

<class ...>
"read-only/write/...." regions="group" />
class>

useage表示使用什麼型別的快取,譬如只讀快取、讀寫快取等等(具體參見Hibernate參考指南),值得注意的時,有部分快取在Hibernate的實現中不支援讀寫快取,譬如JBossCache在Hibernate的實現中只是一種只讀快取,具體快取實現對快取型別的支援情況,可以參見org.hibernate.cache包
regions表示快取分塊,大部分的快取實現往往對快取進行分塊,該部分是可選的,詳細參見各快取實現
方法2:在hibernate.cfg.xml中配置

我認為第二種更好,可以統一管理
2)查詢快取
查詢時候將查詢結果以查詢條件為鍵儲存起來,需要配置如下
A.在hibernate.cfg.xml中配置(啟用查詢快取)
"hibernate.cache.use_query_cache">true   

(前面的屬性名可參見常量
org.hibernate.cfg.Enviroment.USE_QUERY_CACHE)
B.程式
query.setCacheable(true);  
query.setCacheRegions(...);

需要注意的是,查詢快取與物件快取要結合更有效,因為查詢快取僅快取查詢結果列表的主鍵資料
一般情況下在開發中,對一些比較穩定而又被頻繁引用的資料,譬如資料字典之類的,將其進行二級快取,對一些查詢條件和查詢資料變化不頻繁而又常常被使用的查詢,將其進行二級快取。由於二級快取是放在記憶體中,而且Hibernate的快取不是弱引用快取(WeekReference),所以注意不要將大塊的資料放入其中,否則可能會被記憶體造成比較大的壓力。

[b]3.Hibernate效能優化之批量資料操作優化[/b]
當進行大批量資料操作(幾萬甚至幾十幾百萬)時,需要注意兩點,一,批量提交,二,及時清除不需要的一級快取資料
1)所謂的批量提交,就是不要頻繁使用session的flush,每一次進行flush,Hibernate將PO資料於資料庫進行同步,對於海量級資料操作來說是效能災難(同時提交幾千條資料和提交一條資料flush一次效能差別可能會是幾十倍的差異)。一般將資料操作放在事務中,當事務提交時Hibernate自動幫你進行flush操作。
2)及時清除不需要的一級快取資料:由於Hibernate預設採用一級快取,而在session的生命期間,所有資料抓取之後會放入一級快取中,而當資料規模比較龐大時,抓取到記憶體中的資料會讓記憶體壓力非常大,一般分批操作資料,被一次操作之後將一級快取清除,譬如:

session.clear(User.class)
4.雜項
dynamic-insert,dynamic-update,動態插入和動態更新,指的是讓Hibernate插入資料時僅插入非空資料,當修改資料時只修改變化的資料,譬如對於
class User  
{
id
username
password
}

如果u.id=1, u.username="ayufox",u.password=null,那麼如果不設定動態插入,則其sql語句是insert into users(id, username, password) values (1, 'ayufox', '),如果設定則其sql語句是insert into users(username) valeus('ayufox')
在如上的情況下,如果修改u.password='11',那麼如果不設定動態更新,則sql語句為update users set username='ayufox', password='11' where id = 1,如果設定則為update user set password='11' where d = 1
設定是在class的對映檔案中,如下
<class name="User" table="users" dynamic=insert="true/false" dynamic-update="true/false" ...>  
class>