熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Java編程 >> Java開源技術 >> 正文

如何提高hibernate性能

2013-11-23 20:34:43  來源: Java開源技術 

  文章出處

  在一個擁有單獨業務層的應用中業務層必須在返回之前為web層准備好其所需的數據集合這就意味著 業務層應該載入所有表現層/web層所需的數據並將這些已實例化完畢的數據返回通常應用程序應該 為web層所需的每個集合調用Hibernateinitialize() (這個調用必須發生咱session關閉之前) 或者使用帶有FETCH 從句或FetchModeJOIN 的Hibernate查詢 事先取得所有的數據集合如果你在應用中使用了Command模式代替Session Facade 那麼這項任務將會變得簡單的多

  你也可以通過merge() 或lock() 方法在訪問未實例化的集合(或代理)之前 為先前載入的對象綁定一個新的Session 顯然Hibernate將不會也不應該自動完成這些任務因為這將引入一個特殊的事務語義

  有時候你並不需要完全實例化整個大的集合僅需要了解它的部分信息(例如其大小)或者集合的部分內容

  你可以使用集合過濾器得到其集合的大小而不必實例化整個集合

  ( (Integer) screateFilter( collection select count(*) )list()get() )intValue()

  這裡的createFilter() 方法也可以被用來有效的抓取集合的部分內容而無需實例化整個集合

  screateFilter( lazyCollection )setFirstResult()setMaxResults()list();

   使用批量抓取(Using batch fetching)

  Hibernate可以充分有效的使用批量抓取也就是說如果僅一個訪問代理(或集合)那麼Hibernate將不載入其他未實例化的代理 批量抓取是延遲查詢抓取的優化方案你可以在兩種批量抓取方案之間進行選擇在類級別和集合級別

  類/實體級別的批量抓取很容易理解假設你在運行時將需要面對下面的問題你在一個Session 中載入了個 Cat 實例每個Cat 實例都擁有一個引用成員owner 其指向Person 而Person 類是代理同時lazy=true 如果你必須遍歷整個cats集合對每個元素調用getOwner() 方法Hibernate將會默認的執行次SELECT 查詢 得到其owner的代理對象這時你可以通過在映射文件的Person 屬性顯式聲明batchsize 改變其行為

  <class name=Person batchsize=></class>

  隨之Hibernate將只需要執行三次查詢分別為

  你也可以在集合級別定義批量抓取例如如果每個Person 都擁有一個延遲載入的Cats 集合 現在Sesssion 中載入了個person對象遍歷person集合將會引起次SELECT 查詢 每次查詢都會調用getCats() 方法如果你在Person 的映射定義部分允許對cats 批量抓取 那麼Hibernate將可以預先抓取整個集合請看例子

  <class name=Person> <set name=cats batchsize=> </set></class>

  如果整個的batchsize 是(筆誤?)那麼Hibernate將會分四次執行SELECT 查詢 按照的大小分別載入數據這裡的每次載入的數據量還具體依賴於當前Session 中未實例化集合的個數

  如果你的模型中有嵌套的樹狀結構例如典型的帳單-原料結構(billofmaterials pattern)集合的批量抓取是非常有用的(盡管在更多情況下對樹進行讀取時嵌套集合(nested set)或原料路徑(materialized path)(××)是更好的解決方法

   使用子查詢抓取(Using subselect fetching)

  假若一個延遲集合或單值代理需要抓取Hibernate會使用一個subselect重新運行原來的查詢一次性讀入所有的實例這和批量抓取的實現方法是一樣的不會有破碎的加載

   使用延遲屬性抓取(Using lazy property fetching)

  Hibernate 對單獨的屬性支持延遲抓取這項優化技術也被稱為組抓取(fetch groups)請注意該技術更多的屬於市場特性在實際應用中優化行讀取比優化列讀取更重要但是僅載入類的部分屬性在某些特定情況下會有用例如在原有表中擁有幾百列數據數據模型無法改動的情況下

  可以在映射文件中對特定的屬性設置lazy 定義該屬性為延遲載入

  <class name=Document> <id name=id> <generator class=native/> </id> <property name=name notnull=true length=/> <property name=summary notnull=true length= lazy=true/> <property name=text notnull=true length= lazy=true/></class>

  屬性的延遲載入要求在其代碼構建時加入二進制指示指令(bytecode instrumentation)如果你的持久類代碼中未含有這些指令 Hibernate將會忽略這些屬性的延遲設置仍然將其直接載入

  你可以在Ant的Task中進行如下定義對持久類代碼加入二進制指令

  <target name=instrument depends=compile> <taskdef name=instrument classname=orghibernatetoolinstrumentInstrumentTask> <classpath path=${jarpath}/> <classpath path=${classesdir}/> <classpath refid=libclasspath/> </taskdef> <instrument verbose=true> <fileset dir=${testclassesdir}/org/hibernate/auction/model> <include name=*class/> </fileset> </instrument></target>

  還有一種可以優化的方法它使用HQL或條件查詢的投影(projection)特性可以避免讀取非必要的列 這一點至少對只讀事務是非常有用的它無需在代碼構建時二進制指令處理因此是一個更加值得選擇的解決方法

  有時你需要在HQL中通過抓取所有屬性 強行抓取所有內容

   二級緩存(The Second Level Cache)

  Hibernate的Session 在事務級別進行持久化數據的緩存操作 當然也有可能分別為每個類(或集合)配置集群或JVM級別(SessionFactory級別 )的緩存 你甚至可以為之插入一個集群的緩存注意緩存永遠不知道其他應用程序對持久化倉庫(數據庫)可能進行的修改 (即使可以將緩存數據設定為定期失效)

  默認情況下Hibernate使用EHCache進行JVM級別的緩存(目前Hibernate已經廢棄了對JCS的支持未來版本中將會去掉它) 你可以通過設置Hibernatecacheprovider_class 屬性指定其他的緩存策略 該緩存策略必須實現orghibernatecacheCacheProvider 接口

  表   緩存策略提供商(Cache Providers)

Cache Provider class Type Cluster Safe Query Cache Supported Hashtable (not intended for production use) orghibernatecacheHashtableCacheProvider memory   yes EHCache orghibernatecacheEhCacheProvider memory disk   yes OSCache orghibernatecacheOSCacheProvider memory disk   yes SwarmCache orghibernatecacheSwarmCacheProvider clustered (ip multicast) yes (clustered invalidation)   JBoss TreeCache orghibernatecacheTreeCacheProvider clustered (ip multicast) transactional yes (replication) yes (clock sync req)

   緩存映射(Cache mappings)

  類或者集合映射的<cache> 元素可以有下列形式

  <cache usage=transactional|readwrite|nonstrictreadwrite|readonly ()/>

()

  usage 說明了緩存的策略: transactional readwrite nonstrictreadwrite 或 readonly

  另外(首選?) 你可以在hibernatecfgxml中指定<classcache> 和 <collectioncache> 元素

  這裡的usage 屬性指明了緩存並發策略(cache concurrency strategy)

   策略只讀緩存(Strategy: read only)

  如果你的應用程序只需讀取一個持久化類的實例而無需對其修改 那麼就可以對其進行只讀 緩存這是最簡單也是實用性最好的方法甚至在集群中它也能完美地運作

  <class name=egImmutable mutable=false> <cache usage=readonly/> </class>

    策略:讀/寫緩存(Strategy: read/write)

  如果應用程序需要更新數據那麼使用讀/寫緩存 比較合適 如果應用程序要求序列化事務的隔離級別(serializable transaction isolation level)那麼就決不能使用這種緩存策略 如果在JTA環境中使用緩存你必須指定Hibernatetransactionmanager_lookup_class 屬性的值 通過它Hibernate才能知道該應用程序中JTA的TransactionManager 的具體策略 在其它環境中你必須保證在Sessionclose() 或Sessiondisconnect() 調用前 整個事務已經結束 如果你想在集群環境中使用此策略你必須保證底層的緩存實現支持鎖定(locking)Hibernate內置的緩存策略並不支持鎖定功能

  <class name=egCat > <cache usage=readwrite/> <set name=kittens > <cache usage=readwrite/> </set></class>

    策略:非嚴格讀/寫緩存(Strategy: nonstrict read/write)

  如果應用程序只偶爾需要更新數據(也就是說兩個事務同時更新同一記錄的情況很不常見)也不需要十分嚴格的事務隔離 那麼比較適合使用非嚴格讀/寫緩存 策略如果在JTA環境中使用該策略 你必須為其指定Hibernatetransactionmanager_lookup_class 屬性的值 在其它環境中你必須保證在Sessionclose() 或Sessiondisconnect() 調用前 整個事務已經結束

    策略:事務緩存(transactional)

  Hibernate的事務緩存 策略提供了全事務的緩存支持 例如對JBoss TreeCache的支持這樣的緩存只能用於JTA環境中你必須指定 為其Hibernatetransactionmanager_lookup_class 屬性

  沒有一種緩存提供商能夠支持上列的所有緩存並發策略下表中列出了各種提供器及其各自適用的並發策略

  表   各種緩存提供商對緩存並發策略的支持情況(Cache Concurrency Strategy Support)

Cache readonly nonstrictreadwrite readwrite transactional Hashtable (not intended for production use) yes yes yes   EHCache yes yes yes   OSCache yes yes yes   SwarmCache yes yes     JBoss TreeCache yes     yes

    管理緩存(Managing the caches)

  無論何時當你給save() update() 或 saveOrUpdate() 方法傳遞一個對象時或使用load() get() list() iterate() 或scroll() 方法獲得一個對象時 該對象都將被加入到Session 的內部緩存中

  當隨後flush()方法被調用時對象的狀態會和數據庫取得同步 如果你不希望此同步操作發生或者你正處理大量對象需要對有效管理內存時你可以調用evict() 方法從一級緩存中去掉這些對象及其集合

  ScrollableResult cats = sesscreateQuery(from Cat as cat)scroll(); //a huge result setwhile ( catsnext() ) { Cat cat = (Cat) catsget(); doSomethingWithACat(cat); sessevict(cat);}

  Session還提供了一個contains() 方法用來判斷某個實例是否處於當前session的緩存中

  如若要把所有的對象從session緩存中徹底清除則需要調用Sessionclear()

  對於二級緩存來說在SessionFactory 中定義了許多方法 清除緩存中實例整個類集合實例或者整個集合

  sessionFactoryevict(Catclass catId); //evict a particular CatsessionFactoryevict(Catclass); //evict all CatssessionFactoryevictCollection(Catkittens catId); //evict a particular collection of kittenssessionFactoryevictCollection(Catkittens); //evict all kitten collections

  CacheMode 參數用於控制具體的Session如何與二級緩存進行交互


  CacheModeNORMAL 從二級緩存中讀寫數據


  CacheModeGET 從二級緩存中讀取數據僅在數據更新時對二級緩存寫數據


  CacheModePUT 僅向二級緩存寫數據但不從二級緩存中讀數據


  CacheModeREFRESH 僅向二級緩存寫數據但不從二級緩存中讀數據通過 Hibernatecacheuse_minimal_puts 的設置強制二級緩存從數據庫中讀取數據刷新緩存內容

  如若需要查看二級緩存或查詢緩存區域的內容你可以使用統計(Statistics) API

  Map cacheEntries = sessionFactorygetStatistics() getSecondLevelCacheStatistics(regionName) getEntries();

  此時你必須手工打開統計選項可選的你可以讓Hibernate更人工可讀的方式維護緩存內容

  Hibernategenerate_statistics truehibernatecacheuse_structured_entries true

   查詢緩存(The Query Cache)

  查詢的結果集也可以被緩存只有當經常使用同樣的參數進行查詢時這才會有些用處 要使用查詢緩存首先你必須打開它

  Hibernatecacheuse_query_cache true

  該設置將會創建兩個緩存區域 一個用於保存查詢結果集(orghibernatecacheStandardQueryCache ) 另一個則用於保存最近查詢的一系列表的時間戳(orghibernatecacheUpdateTimestampsCache ) 請注意在查詢緩存中它並不緩存結果集中所包含的實體的確切狀態它只緩存這些實體的標識符屬性的值以及各值類型的結果 所以查詢緩存通常會和二級緩存一起使用

  絕大多數的查詢並不能從查詢緩存中受益所以Hibernate默認是不進行查詢緩存的如若需要進行緩存請調用 QuerysetCacheable(true) 方法這個調用會讓查詢在執行過程中時先從緩存中查找結果 並將自己的結果集放到緩存中去

  如果你要對查詢緩存的失效政策進行精確的控制你必須調用QuerysetCacheRegion() 方法 為每個查詢指定其命名的緩存區域

  List blogs = sesscreateQuery(from Blog blog where blogblogger = :blogger) setEntity(blogger blogger) setMaxResults() setCacheable(true) setCacheRegion(frontpages) list();

  如果查詢需要強行刷新其查詢緩存區域那麼你應該調用QuerysetCacheMode(CacheModeREFRESH) 方法 這對在其他進程中修改底層數據(例如不通過Hibernate修改數據)或對那些需要選擇性更新特定查詢結果集的情況特別有用 這是對SessionFactoryevictQueries() 的更為有效的替代方案同樣可以清除查詢緩存區域

    理解集合性能(Understanding Collection performance)

  前面我們已經對集合進行了足夠的討論本段中我們將著重講述集合在運行時的事宜

    分類(Taxonomy)

  Hibernate定義了三種基本類型的集合


  值數據集合


  一對多關聯


  多對多關聯

  這個分類是區分了不同的表和外鍵關系類型但是它沒有告訴我們關系模型的所有內容 要完全理解他們的關系結構和性能特點我們必須同時考慮用於Hibernate更新或刪除集合行數據的主鍵的結構 因此得到了如下的分類


  有序集合類


  集合(sets)


  包(bags)

  所有的有序集合類(maps lists arrays)都擁有一個由<key> 和 <index> 組成的主鍵 這種情況下集合類的更新是非常高效的——主鍵已經被有效的索引因此當Hibernate試圖更新或刪除一行時可以迅速找到該行數據

  集合(sets)的主鍵由<key> 和其他元素字段構成 對於有些元素類型來說這很低效特別是組合元素或者大文本大二進制字段 數據庫可能無法有效的對復雜的主鍵進行索引 另一方面對於一對多多對多關聯特別是合成的標識符來說集合也可以達到同樣的高效性能( 附注如果你希望SchemaExport 為你的<set> 創建主鍵 你必須把所有的字段都聲明為notnull=true

  <idbag> 映射定義了代理鍵因此它總是可以很高效的被更新事實上 <idbag> 擁有著最好的性能表現

  Bag是最差的因為bag允許重復的元素值也沒有索引字段因此不可能定義主鍵 Hibernate無法判斷出重復的行當這種集合被更改時Hibernate將會先完整地移除 (通過一個(in a single DELETE ))整個集合然後再重新創建整個集合 因此Bag是非常低效的

  請注意對於一對多關聯來說主鍵很可能並不是數據庫表的物理主鍵 但就算在此情況下上面的分類仍然是有用的(它仍然反映了Hibernate在集合的各數據行中是如何進行定位

    Lists maps 和sets用於更新效率最高

  根據我們上面的討論顯然有序集合類型和大多數set都可以在增加刪除修改元素中擁有最好的性能

  可論證的是對於多對多關聯值數據集合而言有序集合類比集合(set)有一個好處因為Set 的內在結構 如果改變了一個元素Hibernate並不會更新(UPDATE) 這一行 對於Set 來說只有在插入(INSERT) 和刪除(DELETE) 操作時改變才有效再次強調這段討論對一對多關聯並不適用

  注意到數組無法延遲載入我們可以得出結論list map和idbags是最高效的(非反向)集合類型set則緊隨其後 在Hibernate中set應該時最通用的集合類型這時因為set的語義在關系模型中是最自然的

  但是在設計良好的Hibernate領域模型中我們通常可以看到更多的集合事實上是帶有inverse=true 的一對多的關聯對於這些關聯更新操作將會在多對一的這一端進行處理因此對於此類情況無需考慮其集合的更新性能

    Bag和list是反向集合類中效率最高的

  在把bag扔進水溝之前你必須了解在一種情況下bag的性能(包括list)要比set高得多 對於指明了inverse=true 的集合類(比如說標准的雙向的一對多關聯) 我們可以在未初始化(fetch)包元素的情況下直接向bag或list添加新元素! 這是因為Collectionadd() )或者CollectionaddAll() 方法 對bag或者List總是返回true(這點與與Set不同)因此對於下面的相同代碼來說速度會快得多

  Parent p = (Parent) sessload(Parentclass id); Child c = new Child(); csetParent(p); pgetChildren()add(c); //no need to fetch the collection! sessflush();

    一次性刪除(One shot delete)

  偶爾的逐個刪除集合類中的元素是相當低效的Hibernate並沒那麼笨 如果你想要把整個集合都刪除(比如說調用listclear())Hibernate只需要一個DELETE就搞定了

  假設我們在一個長度為的集合類中新增加了一個元素然後再刪除兩個 Hibernate會安排一條INSERT 語句和兩條DELETE 語句(除非集合類是一個bag) 這當然是顯而易見的 但是假設我們刪除了個數據只剩下然後新增則有兩種處理方式


  逐一的刪除這個數據再新增三個


  刪除整個集合類(只用一句DELETE語句)然後增加個數據

  Hibernate還沒那麼聰明知道第二種選擇可能會比較快 (也許讓Hibernate不這麼聰明也是好事否則可能會引發意外的數據庫觸發器之類的問題

  幸運的是你可以強制使用第二種策略你需要取消原來的整個集合類(解除其引用) 然後再返回一個新的實例化的集合類只包含需要的元素有些時候這是非常有用的

  顯然一次性刪除並不適用於被映射為inverse=true 的集合

    監測性能(Monitoring performance)

  沒有監測和性能參數而進行優化是毫無意義的Hibernate為其內部操作提供了一系列的示意圖因此可以從 每個SessionFactory 抓取其統計數據

    監測SessionFactory

  你可以有兩種方式訪問SessionFactory 的數據記錄第一種就是自己直接調用 sessionFactorygetStatistics() 方法讀取顯示統計 數據

  此外如果你打開StatisticsService MBean選項那麼Hibernate則可以使用JMX技術 發布其數據記錄你可以讓應用中所有的SessionFactory 同時共享一個MBean也可以每個 SessionFactory分配一個MBean下面的代碼即是其演示代碼

  // MBean service registration for a specific SessionFactoryHashtable tb = new Hashtable();tbput(type statistics);tbput(sessionFactory myFinancialApp);ObjectName on = new ObjectName(hibernate tb); // MBean object nameStatisticsService stats = new StatisticsService(); // MBean implementationstatssetSessionFactory(sessionFactory); // Bind the stats to a SessionFactoryserverregisterMBean(stats on); // Register the Mbean on the server

  // MBean service registration for all SessionFactorysHashtable tb = new Hashtable();tbput(type statistics);tbput(sessionFactory all);ObjectName on = new ObjectName(hibernate tb); // MBean object nameStatisticsService stats = new StatisticsService(); // MBean implementationserverregisterMBean(stats on); // Register the MBean on the server

  TODO仍需要說明的是在第一個例子中我們直接得到和使用MBean而在第二個例子中在使用MBean之前 我們則需要給出SessionFactory的JNDI名使用hibernateStatsBeansetSessionFactoryJNDIName(my/JNDI/Name) 得到SessionFactory然後將MBean保存於其中

  你可以通過以下方法打開或關閉SessionFactory 的監測功能


  在配置期間將Hibernategenerate_statistics 設置為true 或false


  在運行期間則可以可以通過sfgetStatistics()setStatisticsEnabled(true) 或hibernateStatsBeansetStatisticsEnabled(true)

  你也可以在程序中調用clear() 方法重置統計數據調用logSummary() 在日志中記錄(info級別)其總結

    數據記錄(Metrics)

  Hibernate提供了一系列數據記錄其記錄的內容包括從最基本的信息到與具體場景的特殊信息所有的測量值都可以由 Statistics 接口進行訪問主要分為三類


  使用Session 的普通數據記錄例如打開的Session的個數取得的JDBC的連接數等


  實體集合查詢緩存等內容的統一數據記錄


  和具體實體集合查詢緩存相關的詳細數據記錄

  例如你可以檢查緩存的命中成功次數緩存的命中失敗次數實體集合和查詢的使用概率查詢的平均時間等請注意 Java中時間的近似精度是毫秒Hibernate的數據精度和具體的JVM有關在有些平台上其精度甚至只能精確到

  你可以直接使用getter方法得到全局數據記錄(例如和具體的實體集合緩存區無關的數據)你也可以在具體查詢中通過標記實體名 或HQLSQL語句得到某實體的數據記錄請參考Statistics EntityStatistics CollectionStatistics SecondLevelCacheStatistics 和QueryStatistics 的API文檔以抓取更多信息下面的代碼則是個簡單的例子

  Statistics stats = HibernateUtilsessionFactorygetStatistics();double queryCacheHitCount = statsgetQueryCacheHitCount();double queryCacheMissCount = statsgetQueryCacheMissCount();double queryCacheHitRatio = queryCacheHitCount / (queryCacheHitCount + queryCacheMissCount);(Query Hit ratio: + queryCacheHitRatio);EntityStatistics entityStats = statsgetEntityStatistics( CatclassgetName() );long changes = entityStatsgetInsertCount() + entityStatsgetUpdateCount() + entityStatsgetDeleteCount();(CatclassgetName() + changed + changes + times );

  如果你想得到所有實體集合查詢和緩存區的數據你可以通過以下方法獲得實體集合查詢和緩存區列表 getQueries() getEntityNames() getCollectionRoleNames() 和 getSecondLevelCacheRegionNames()

  


From:http://tw.wingwit.com/Article/program/Java/ky/201311/28706.html
    推薦文章
    Copyright © 2005-2013 電腦知識網 Computer Knowledge   All rights reserved.