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