[摘要] 文中對(duì)Hibernate檢索策略中內(nèi)存浪費(fèi)的原因進(jìn)行了研究,提出了采用查詢緩存和集合過濾的方法進(jìn)行查詢優(yōu)化, 降低了訪問數(shù)據(jù)庫的頻率且避免了在數(shù)據(jù)查詢檢索過程中加載不需要的Java對(duì)象,從而降低了的內(nèi)存消耗。最后,通過查詢網(wǎng)上購物系統(tǒng)中客戶的定單記錄試驗(yàn)驗(yàn)證了優(yōu)化方式的有效性。
[關(guān)鍵詞] Hibernate 查詢緩存 集合過濾 客戶 定單
在分層的軟件架構(gòu)中持久化層封裝了所有數(shù)據(jù)訪問細(xì)節(jié),是對(duì)象-關(guān)系映射(ORM)的中間件,Hibernate是一種ORM中間件工具,它對(duì)JDBC API進(jìn)行了封裝,負(fù)責(zé)Java對(duì)象的持久化。Hibernate通過Session接口提供了基本的保存、更新、刪除和查詢,Session具有一個(gè)緩存,位于緩存中的對(duì)象處于持久化狀態(tài),它和數(shù)據(jù)庫中的相關(guān)記錄對(duì)應(yīng),Session能夠在某些時(shí)間點(diǎn)按照緩存中持久化對(duì)象的屬性變化來同步更新數(shù)據(jù)庫。在Session的緩存中存放的是相互關(guān)聯(lián)的對(duì)象圖。在默認(rèn)情況下,當(dāng)Hibernate從數(shù)據(jù)庫中加載Java對(duì)象時(shí),會(huì)同時(shí)加載所有關(guān)聯(lián)的Java對(duì)象,從而影響了系統(tǒng)的性能。本文以查詢網(wǎng)上購物系統(tǒng)中的客戶(Customer)和定單(Order)信息為例,介紹如何設(shè)置Hibernate的檢索策略,以優(yōu)化查詢性能。
一、常用查詢方法
設(shè)計(jì)兩個(gè)數(shù)據(jù)庫表, 表名為CUSTOMERS和ORDERS, 它們包含的基本字段及之間的關(guān)系如圖1:
Hibernate查詢客戶和定單信息的步驟為:
1.運(yùn)用反射機(jī)制,獲得customer對(duì)象的類型Customer.class。
2.參考對(duì)象-關(guān)系映射元數(shù)據(jù),了解到和Customer類對(duì)應(yīng)的表為CUSTOMERS表,類Customer與類Order關(guān)聯(lián),類Order和ORDERS表對(duì)應(yīng),ORDERS表中外鍵CUSTOMER_ID參照CUSTOMERS表的主鍵ID。
3.根據(jù)映射信息,生成SQL語句:
select *from CUSTOMERS;
select *from ORDERS where CUSTOMERS_ID=1。
4.調(diào)用JDBC API,執(zhí)行以上SQL語句。Hibernate在檢索與Customer關(guān)聯(lián)的Order對(duì)象時(shí),使用默認(rèn)的立即檢索策略。這種檢索策略存在三大不足:
(1)select語句的數(shù)目太多需要頻繁地訪問數(shù)據(jù)庫,會(huì)影響檢索性能。
(2)在應(yīng)用邏輯只需要訪問Customer對(duì)象,而不需要訪問Order對(duì)象的場(chǎng)合,加載Order對(duì)象完全是多余的操作,這些多余的Order對(duì)象浪費(fèi)了許多內(nèi)存空間。
(3)假定這個(gè)Customer對(duì)象與500個(gè)Order對(duì)象關(guān)聯(lián),就會(huì)加載500個(gè)Order對(duì)象。在實(shí)際應(yīng)用中往往只需要訪問Orders集合中的部分Order對(duì)象.例如訪問客戶定單金額大于100的Order對(duì)象,此時(shí)調(diào)用customer.getOrders().iterator()方法會(huì)影響運(yùn)行時(shí)性能.因?yàn)樗鼤?huì)加載應(yīng)用程序不需要訪問的Order對(duì)象。
延遲檢索策略能避免多余加載應(yīng)用程序不需要訪問的關(guān)聯(lián)對(duì)象;但當(dāng)采用延遲檢索策略時(shí),應(yīng)用程序如果希望訪問游離狀態(tài)的代理類實(shí)例,必須保證它作持久化狀態(tài)時(shí)已經(jīng)被初始化。迫切左外連接檢索策略則利用SQL的外連接查詢功能能夠減少select語句的數(shù)目;但缺點(diǎn)在于可能會(huì)加載應(yīng)用程序不需要訪問的對(duì)象,浪費(fèi)許多內(nèi)存空間,更雜的數(shù)據(jù)庫表連接也會(huì)影響檢索性能。
二、查詢性能優(yōu)化
Hibernate主要可從以下兩方面來優(yōu)化查詢性能。
1.使用查詢緩存降低訪問數(shù)據(jù)庫的頻率,減少select語句的數(shù)目;對(duì)于經(jīng)常使用的查詢語句如果啟用了查詢緩存.當(dāng)?shù)谝淮螆?zhí)行查詢語句時(shí),Hibernate會(huì)把查詢結(jié)果存放在第二級(jí)緩存中。以后再次執(zhí)行該查詢語句時(shí).只需從緩存中獲得查詢結(jié)果從而提高查詢性能。對(duì)查詢語句啟用查詢緩存的步驟如下:
(1)配置第二級(jí)緩存,在Customer.hbm.xml和 Order.hbm.xml映射文件中分別為Customer類、Customer類的orders集合以及Order類設(shè)置第二級(jí)緩存,
Customer.hbm.xml設(shè)置第二級(jí)緩存代碼: name=“orders” > Order.hbm.xml設(shè)置第二級(jí)緩存代碼: (2)在Hibernate的配置文件hibernate.properties中選用EHCache和設(shè)置查詢緩存屬性: hibernate.cache.provider_class=net.sf.hibernate.cache.EhCacheProvi Hibernate.cache.use_query_cache=ture (3)對(duì)于希望啟用查詢?cè)娴牟樵冋Z句.調(diào)用接口Query的setCacheable()方法: Query orderByMoneyQuery=session.createQuery(“from Order o where o.money>:money”); orderByMoneyQuery.setInteger(“money”, money); orderByMoneyQuery. setCacheable(true); 如果希望更加精粒度地控制查詢?cè)?,可以設(shè)置緩存區(qū)域. orderByMoneyQuery.setCacheRegion(“orderQueries”); Hibernate提供了三種和查詢相關(guān)的緩存區(qū)域 ①默認(rèn)的查詢緩存區(qū)域:net.sf. hibernate.cache.StandardQueryCache ②用戶自定義的查詢緩存區(qū)域:如“orderQueries” ③時(shí)間戳緩存區(qū)域: net.sf. hibernate.cache.UpdateTimestampCache 默認(rèn)的查詢緩存區(qū)域以及用戶自定義的查詢緩存區(qū)域部用于存放查詢結(jié)果。而時(shí)間戳緩存區(qū)域存放了對(duì)與查詢結(jié)果相關(guān)的表進(jìn)行插入、更新或刪除操作的時(shí)間戳。Hibernate通過時(shí)間戳緩存區(qū)域來判斷被緩存的查詢結(jié)果是否過期,它的運(yùn)行過程如下: 在T1時(shí)刻執(zhí)行查詢語句,把查詢結(jié)果存放在QueryCache域.該區(qū)域的時(shí)間戳為TI時(shí)刻。 在T2時(shí)刻對(duì)與查詢結(jié)果相關(guān)的表進(jìn)行插入、更新或刪除操作。Hibernate把T2時(shí)刻存放在UpdateTimestampCache區(qū)域。 在T3時(shí)刻執(zhí)行查詢語句前,先比較QueryCache區(qū)域的時(shí)間戳和UpdateTimestampCache區(qū)域的時(shí)間戳.如果T2>T1,那么就丟棄原先存放在QueryCache區(qū)域的查詢結(jié)果,重新到數(shù)據(jù)庫中查詢數(shù)據(jù)再把查詢結(jié)果存放在QueryCache區(qū)域;如果T2<T1,直接從QueryCache區(qū)域獲得查詢結(jié)果。 由此可見,如果當(dāng)前應(yīng)用進(jìn)程對(duì)數(shù)據(jù)庫的相關(guān)數(shù)據(jù)做了修改,Hibernate會(huì)自動(dòng)刷新緩存的查詢結(jié)果。若其他應(yīng)用進(jìn)程對(duì)數(shù)據(jù)庫的相關(guān)數(shù)據(jù)做了修改,Hibernate無法監(jiān)測(cè)到這一變化,此時(shí)由應(yīng)用程序負(fù)責(zé)監(jiān)測(cè)這一變化(如通過發(fā)送和接收事件或消息機(jī)制),然后手工刷新查詢結(jié)果。Query接口的setForceCacheRefresh(true)方法允許手工刷新查詢結(jié)果,它使得Hibernate丟棄查詢緩存區(qū)域中已有的查詢結(jié)果,重新到數(shù)據(jù)庫中查詢數(shù)據(jù).再把查詢結(jié)果存放在查詢緩存區(qū)域中。 2.使用集合過濾避免多余加載程序不需要訪問的數(shù)據(jù) 使用集合過濾可以很好避免加載多余的Order對(duì)象,其主要代碼如下: List result=session.createFilter(customer.getOrders(),“where this.money>100 order by this.money”).list(); Iterator it=result.iterator(); While(it.hasNext()){ Order order=(Order)it.next(); …… } 代碼中Session接口的createFilter方法用來過濾集合,它具有以下優(yōu)點(diǎn): (1)如果Customer對(duì)象orders集會(huì)已經(jīng)被初始化,為了保證Session的緩存中不會(huì)出現(xiàn)OID相同的Order對(duì)象,Query的list()方法不會(huì)再創(chuàng)建Order對(duì)象,僅僅返回已經(jīng)存在的Order對(duì)象的引用。其運(yùn)行時(shí)行為如圖2所示: (2)如果Customer對(duì)象的orders集合還沒有被初始化,Query的list()方法會(huì)創(chuàng)建相應(yīng)的Order對(duì)象。但是不會(huì)初始化Customer對(duì)象的orders集合。其運(yùn)行時(shí)行為如圖3所示: 三、試驗(yàn)分析 這里通過檢索某客戶定單金額大于100元的所有定單試驗(yàn)來驗(yàn)證改進(jìn)前后的性能情況。 按照立即檢索策略我們一次從數(shù)據(jù)庫中讀取客戶ID=1的所有訂單(共500條記錄)中金額大于100元的所有記錄(共200條記錄),然后按照改進(jìn)后的方式我們從數(shù)據(jù)庫中讀取上述記錄??紤]到Java虛擬機(jī)的垃圾收集機(jī)制和降低試驗(yàn)誤差,兩種的檢索方式分別做五次,取平均值。開發(fā)軟件采jdk1.5.0+Tomcat5.0.24,數(shù)據(jù)庫采用MySQL5.0.2。 結(jié)果如下: 1.采用立即檢索策略 第一次查詢前、后Java虛擬機(jī)內(nèi)存占用率分別為:23065K、26643K,查詢結(jié)果占3578K; 第二次查詢前、后Java虛擬機(jī)內(nèi)存占用率分別為:22990K、26662K,查詢結(jié)果占用3672K; 第三次查詢前、后Java虛擬機(jī)內(nèi)存占用率分別為:22998K、26580K,查詢結(jié)果占用3590K; 第四次查詢前、后Java虛擬機(jī)內(nèi)存占用率分別為:22967K、26597K,查詢結(jié)果占用3612K; 第五次查詢前、后Java虛擬機(jī)內(nèi)存占用率分別為:22978K、26574K,查詢結(jié)果占用3596K; 查詢結(jié)果平均內(nèi)存占用為3609K。 2.同時(shí)采用查詢緩存和集合過濾檢索策略 第一次查詢前、后Java虛擬機(jī)內(nèi)存占用率分別為:23070K、24032K,查詢結(jié)果占用962K; 第二次查詢前、后Java虛擬機(jī)內(nèi)存占用率分別為:23012K、23909K,查詢結(jié)果占用897K; 第三次查詢前、后Java虛擬機(jī)內(nèi)存占用率分別為:22993K、23949K,查詢結(jié)果占用956K; 第四次查詢前、后Java虛擬機(jī)內(nèi)存占用率分別為:22812K、23747K,查詢結(jié)果占用935K; 第五次查詢前、后Java虛擬機(jī)內(nèi)存占用率分別為:22991K、23904K,查詢結(jié)果占用913K; 查詢結(jié)果平均內(nèi)存占用為932K。 從試驗(yàn)結(jié)果可知:采用本文所述的方式進(jìn)行查詢優(yōu)化操作,節(jié)約了大量的內(nèi)存,當(dāng)進(jìn)行大量數(shù)據(jù)查詢時(shí),效果十分明顯。 四、結(jié)語 Hibernate在持久化過程中由于采用立即檢索策略,導(dǎo)致加載了不需要的Java 對(duì)象和頻繁訪問數(shù)據(jù)庫,占用了大量的內(nèi)存。采用本文介紹的查詢緩存和集合過濾檢索策略,可以解決立即檢索策略中存在的問題,并且沒有增加開發(fā)人員的工作量,是減少內(nèi)存消耗的有效方法。 參考文獻(xiàn): [1]唐慕瑾徐伯慶孫國(guó)強(qiáng):Java 類的動(dòng)態(tài)裝載機(jī)制及其在設(shè)計(jì)模式中的應(yīng)用[J].上海理工大學(xué)學(xué)報(bào), 2004,26(1):80-84 [2]孫衛(wèi)琴:精通HIBERNATE: Java對(duì)象持久化技術(shù)詳解[M].北京:電子工業(yè)出版社,2005 [3]何錚陳志剛:對(duì)象/關(guān)系映射框架的研究與應(yīng)用[J].計(jì)算機(jī)工程與應(yīng)用, 2003,39 (26):188-191,194 [4]Hibernate 官方網(wǎng)站. [EB/OL]http://www.hibernate.org