Hibernate優(yōu)化方法解析
Hibernate優(yōu)化方法一:批量修改和刪除
在Hibernate 2中,如果需要對任何數(shù)據(jù)進行修改和刪除操作,都需要先執(zhí)行查詢操作,在得到要修改或者刪除的數(shù)據(jù)后,再對該數(shù)據(jù)進行相應的操作處理。在數(shù)據(jù)量少的情況下采用這種處理方式沒有問題,但需要處理大量數(shù)據(jù)的時候就可能存在以下的問題:
◆占用大量的內存。
◆需要多次執(zhí)行update/delete語句,而每次執(zhí)行只能處理一條數(shù)據(jù)。
以上兩個問題的出現(xiàn)會嚴重影響系統(tǒng)的性能。因此,在Hibernate 3中引入了用于批量更新或者刪除數(shù)據(jù)的HQL語句。這樣,開發(fā)人員就可以一次更新或者刪除多條記錄,而不用每次都一個一個地修改或者刪除記錄了。
如果要刪除所有的User對象(也就是User對象所對應表中的記錄),則可以直接使用下面的HQL語句:
delete User
而在執(zhí)行這個HQL語句時,需要調用Query對象的executeUpdate()方法,具體的實例如下所示:
String HQL="delete User";
Query query=session.createQuery(HQL);
int size=query.executeUpdate();
采用這種方式進行數(shù)據(jù)的修改和刪除時與直接使用JDBC的方式在性能上相差無幾,是推薦使用的正確方法。
如果不能采用HQL語句進行大量數(shù)據(jù)的修改,也就是說只能使用取出再修改的方式時,也會遇到批量插入時的內存溢出問題,所以也要采用上面所提供的處理方法來進行類似的處理。
Hibernate優(yōu)化方法二:使用SQL執(zhí)行批量操作
在進行批量插入、修改和刪除操作時,直接使用JDBC來執(zhí)行原生態(tài)的SQL語句無疑會獲得最佳的性能,這是因為在處理的過程中省略或者簡化了以下處理內容:
● HQL語句到SQL語句的轉換。
● Java對象的初始化。
● Java對象的緩存處理。
但是在直接使用JDBC執(zhí)行SQL語句時,有一個最重要的問題就是要處理緩存中的Java對象。因為通過這種底層方式對數(shù)據(jù)的修改將不能通知緩存去進行相應的更新操作,以保證緩存中的對象與數(shù)據(jù)庫中的數(shù)據(jù)是一致的。
Hibernate優(yōu)化方法三:提升數(shù)據(jù)庫查詢的性能
數(shù)據(jù)庫查詢性能的提升也是涉及到開發(fā)中的各個階段,在開發(fā)中選用正確的查詢方法無疑是最基礎也最簡單的。
1 、SQL語句的優(yōu)化
使用正確的SQL語句可以在很大程度上提高系統(tǒng)的查詢性能。獲得同樣數(shù)據(jù)而采用不同方式的SQL語句在性能上的差距可能是十分巨大的。
由于Hibernate是對JDBC的封裝,SQL語句的產生都是動態(tài)由Hibernate自動完成的。Hibernate產生SQL語句的方式有兩種:一種是通過開發(fā)人員編寫的HQL語句來生成,另一種是依據(jù)開發(fā)人員對關聯(lián)對象的訪問來自動生成相應的SQL語句。
至于使用什么樣的SQL語句可以獲得更好的性能要依據(jù)數(shù)據(jù)庫的結構以及所要獲取數(shù)據(jù)的具體情況來進行處理。在確定了所要執(zhí)行的SQL語句后,可以通過以下三個方面來影響Hibernate所生成的SQL語句:
◆HQL語句的書寫方法。
◆查詢時所使用的查詢方法。
◆對象關聯(lián)時所使用的抓取策略。
2 、使用正確的查詢方法
在前面已經介紹過,執(zhí)行數(shù)據(jù)查詢功能的基本方法有兩種:一種是得到單個持久化對象的get()方法和load()方法,另一種是Query對象的list()方法和iterator()方法。在開發(fā)中應該依據(jù)不同的情況選用正確的方法。
get()方法和load()方法的區(qū)別在于對二級緩存的使用上。load()方法會使用二級緩存,而get()方法在一級緩存沒有找到的情況下會直接查詢數(shù)據(jù)庫,不會去二級緩存中查找。在使用中,對使用了二級緩存的對象進行查詢時最好使用load()方法,以充分利用二級緩存來提高檢索的效率。
list()方法和iterator()方法之間的區(qū)別可以從以下幾個方面來進行比較。
◆執(zhí)行的查詢不同
list()方法在執(zhí)行時,是直接運行查詢結果所需要的查詢語句,而iterator()方法則是先執(zhí)行得到對象ID的查詢,然后再根據(jù)每個ID值去取得所要查詢的對象。因此,對于list()方式的查詢通常只會執(zhí)行一個SQL語句,而對于iterator()方法的查詢則可能需要執(zhí)行N+1條SQL語句(N為結果集中的記錄數(shù))。
iterator()方法只是可能執(zhí)行N+1條數(shù)據(jù),具體執(zhí)行SQL語句的數(shù)量取決于緩存的情況以及對結果集的訪問情況。
◆緩存的使用
list()方法只能使用二級緩存中的查詢緩存,而無法使用二級緩存對單個對象的緩存(但是會把查詢出的對象放入二級緩存中)。所以,除非重復執(zhí)行相同的查詢操作,否則無法利用緩存的機制來提高查詢的效率。
iterator()方法則可以充分利用二級緩存,在根據(jù)ID檢索對象的時候會首先到緩存中查找,只有在找不到的情況下才會執(zhí)行相應的查詢語句。所以,緩存中對象的存在與否會影響到SQL語句的執(zhí)行數(shù)量。
◆對于結果集的處理方法不同
list()方法會一次獲得所有的結果集對象,而且它會依據(jù)查詢的結果初始化所有的結果集對象。這在結果集非常大的時候必然會占據(jù)非常多的內存,甚至會造成內存溢出情況的發(fā)生。
iterator()方法在執(zhí)行時不會一次初始化所有的對象,而是根據(jù)對結果集的訪問情況來初始化對象。因此在訪問中可以控制緩存中對象的數(shù)量,以避免占用過多緩存,導致內存溢出情況的發(fā)生。使用iterator()方法的另外一個好處是,如果只需要結果集中的部分記錄,那么沒有被用到的結果對象根本不會被初始化。所以,對結果集的訪問情況也是調用iterator()方法時執(zhí)行數(shù)據(jù)庫SQL語句多少的一個因素。
所以,在使用Query對象執(zhí)行數(shù)據(jù)查詢時應該從以上幾個方面去考慮使用何種方法來執(zhí)行數(shù)據(jù)庫的查詢操作。
Hibernate優(yōu)化方法四:使用正確的抓取策略
所謂抓取策略(fetching strategy)是指當應用程序需要利用關聯(lián)關系進行對象獲取的時候,Hibernate獲取關聯(lián)對象的策略。抓取策略可以在O/R映射的元數(shù)據(jù)中聲明,也可以在特定的HQL或條件查詢中聲明。
Hibernate 3定義了以下幾種抓取策略。
連接抓取(Join fetching)
連接抓取是指Hibernate在獲得關聯(lián)對象時會在SELECT語句中使用外連接的方式來獲得關聯(lián)對象。
查詢抓取(Select fetching)
查詢抓取是指Hibernate通過另外一條SELECT語句來抓取當前對象的關聯(lián)對象的方式。這也是通過外鍵的方式來執(zhí)行數(shù)據(jù)庫的查詢。與連接抓取的區(qū)別在于,通常情況下這個SELECT語句不是立即執(zhí)行的,而是在訪問到關聯(lián)對象的時候才會執(zhí)行。
子查詢抓取(Subselect fetching)
子查詢抓取也是指Hibernate通過另外一條SELECT語句來抓取當前對象的關聯(lián)對象的方式。與查詢抓取的區(qū)別在于它所采用的SELECT語句的方式為子查詢,而不是通過外連接。
批量抓取(Batch fetching)
批量抓取是對查詢抓取的優(yōu)化,它會依據(jù)主鍵或者外鍵的列表來通過單條SELECT語句實現(xiàn)管理對象的批量抓取。
以上介紹的是Hibernate 3所提供的抓取策略,也就是抓取關聯(lián)對象的手段。為了提升系統(tǒng)的性能,在抓取關聯(lián)對象的時機上,還有以下一些選擇。
立即抓取(Immediate fetching)
立即抓取是指宿主對象被加載時,它所關聯(lián)的對象也會被立即加載。
延遲集合抓取(Lazy collection fetching)
延遲集合抓取是指在加載宿主對象時,并不立即加載它所關聯(lián)的對象,而是到應用程序訪問關聯(lián)對象的時候才抓取關聯(lián)對象。這是集合關聯(lián)對象的默認行為。
延遲代理抓取(Lazy proxy fetching)
延遲代理抓取是指在返回單值關聯(lián)對象的情況下,并不在對其進行get操作時抓取,而是直到調用其某個方法的時候才會抓取這個對象。
延遲屬性加載(Lazy attribute fetching)
延遲屬性加載是指在關聯(lián)對象被訪問的時候才進行關聯(lián)對象的抓取。
介紹了Hibernate所提供的關聯(lián)對象的抓取方法和抓取時機,這兩個方面的因素都會影響Hibernate的抓取行為,最重要的是要清楚這兩方面的影響是不同的,不要將這兩個因素混淆,在開發(fā)中要結合實際情況選用正確的抓取策略和合適的抓取時機。
◆抓取時機的選擇
在Hibernate 3中,對于集合類型的關聯(lián)在默認情況下會使用延遲集合加載的抓取時機,而對于返回單值類型的關聯(lián)在默認情況下會使用延遲代理抓取的抓取時機。
對于立即抓取在開發(fā)中很少被用到,因為這很可能會造成不必要的數(shù)據(jù)庫操作,從而影響系統(tǒng)的性能。當宿主對象和關聯(lián)對象總是被同時訪問的時候才有可能會用到這種抓取時機。另外,使用立即連接抓取可以通過外連接來減少查詢SQL語句的數(shù)量,所以,也會在某些特殊的情況下使用。
然而,延遲加載又會面臨另外一個問題,如果在Session關閉前關聯(lián)對象沒有被實例化,那么在訪問關聯(lián)對象的時候就會拋出異常。處理的方法就是在事務提交之前就完成對關聯(lián)對象的訪問。
所以,在通常情況下都會使用延遲的方式來抓取關聯(lián)的對象。因為每個立即抓取都會導致關聯(lián)對象的立即實例化,太多的立即抓取關聯(lián)會導致大量的對象被實例化,從而占用過多的內存資源。
◆抓取策略的選取
對于抓取策略的選取將影響到抓取關聯(lián)對象的方式,也就是抓取關聯(lián)對象時所執(zhí)行的SQL語句。這就要根據(jù)實際的業(yè)務需求、數(shù)據(jù)的數(shù)量以及數(shù)據(jù)庫的結構來進行選擇了。
在這里需要注意的是,通常情況下都會在執(zhí)行查詢的時候針對每個查詢來指定對其合適的抓取策略。指定抓取策略的方法如下所示:
User user = (User) session.createCriteria(User.class)
.setFetchMode("permissions", FetchMode.JOIN)
.add( Restrictions.idEq(userId) )
.uniqueResult();
Hibernate優(yōu)化方法五:查詢性能提升小結
在本小節(jié)中介紹了查詢性能提升的方法,關鍵是如何通過優(yōu)化SQL語句來提升系統(tǒng)的查詢性能。查詢方法和抓取策略的影響也是通過執(zhí)行查詢方式和SQL語句的多少來改變系統(tǒng)的性能的。這些都屬于開發(fā)人員所應該掌握的基本技能,避免由于開發(fā)不當而導致系統(tǒng)性能的低下。
在性能調整中,除了前面介紹的執(zhí)行SQL語句的因素外,對于緩存的使用也會影響系統(tǒng)的性能。通常來說,緩存的使用會增加系統(tǒng)查詢的性能,而降低系統(tǒng)增加、修改和刪除操作的性能(因為要進行緩存的同步處理)。所以,開發(fā)人員應該能夠正確地使用有效的緩存來提高數(shù)據(jù)查詢的性能,而要避免濫用緩存而導致的系統(tǒng)性能變低。在采用緩存的時候也應該注意調整自己的檢索策略和查詢方法,這三者配合起來才可以達到最優(yōu)的性能。
另外,事務的使用策略也會影響到系統(tǒng)的性能。選取正確的事務隔離級別以及使用。
【編輯推薦】