有人測試Hibernate的時間消耗基本是jdbc的1.4倍。其中我認為比較大的一個問題就是cache的記憶體占用,最基本的,hibernate會在session-level的cache中儲存所有新insert的object,而其實這些新插入的object在一般的web系統中是不會馬上用到的。
hibernate3.2為了解決這個問題,新加入了StatelessSession實作,StatelessSession提供的是Command-Oriented API,它取出的對象是全部detached的。它與cache無關,與dirty-checking無關,與association無關,與Collection無關,與interceptor無關。
使用與業務有關的鍵值來實作equals()和 hashCode() .
Session實作了異步write-behind,它允許Hibernate顯式地寫操作的批處理。 這裡,我給出Hibernate如何實作批量插入的方法:
首先,我們設定一個合理的JDBC批處理大小,hibernate.jdbc.batch_size 20。 然後在一定間隔對Session進行flush()和clear()。
Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); for ( int i=0; i<100000; i++ ) { Customer customer = new Customer(.....); session.save(customer); if ( i % 20 == 0 ) { //flush 插入資料和釋放記憶體: session.flush(); session.clear(); } } tx.commit(); session.close(); |
Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); ScrollableResults customers = session.getNamedQuery("GetCustomers") .scroll(ScrollMode.FORWARD_ONLY); int count=0; while ( customers.next() ) { Customer customer = (Customer) customers.get(0); customer.updateStuff(...); if ( ++count % 20 == 0 ) { //flush 更新資料和釋放記憶體: session.flush(); session.clear(); } } tx.commit(); session.close(); |
第 19 章 最佳實踐(Best Practices)
- 設計細顆粒度的持久類并且使用<component>來實作映射。
- 使用一個Address持久類來封裝 street, suburb, state, postcode. 這将有利于代碼重用和簡化代碼重構(refactoring)的工作。 對持久類聲明辨別符屬性。
- Hibernate中辨別符屬性是可選的,不過有很多原因來說明你應該使用辨別符屬性。我們建議辨別符應該是“人造”的(自動生成,不涉及業務含義),并且不是基本類型。為了最大的靈活性,應該使用java.lang.Long or java.lang.String 為每個持久類寫一個映射檔案
- 不要把所有的持久類映射都寫到一個大檔案中。把 com.eg.Foo 映射到com/eg/Foo.hbm.xml中, 在團隊開發環境中,這一點顯得特别有意義。 把映射檔案作為資源加載
- 把映射檔案和他們的映射類放在一起進行部署。 考慮把查詢字元串放在程式外面
- 如果你的查詢中調用了非ANSI标準的SQL函數,那麼這條實踐經驗對你适用。把查詢字元串放在映射檔案中可以讓程式具有更好的可移植性。 使用綁定變量
- 就像在JDBC程式設計中一樣,應該總是用占位符"?"來替換非常量值,不要在查詢中用字元串值來構造非常量值!更好的辦法是在查詢中使用命名參數。 不要自己來管理JDBC connections
- Hibernate允許應用程式自己來管理JDBC connections,但是應該作為最後沒有辦法的辦法。如果你不能使用Hibernate内建的connections providers,那麼考慮實作自己來實作net.sf.hibernate.connection.ConnectionProvider 考慮使用使用者自定義類型(custom type)
- net.sf.hibernate.UserType. This approach frees the application code from implementing transformations to / from a Hibernate type. 假設你有一個Java類型,來自某些類庫,需要被持久化,但是該類沒有提供映射操作需要的存取方法。那麼你應該考慮實作net.sf.hibernate.UserType接口。這種辦法使程式代碼寫起來更加自如,不再需要考慮類與Hibernate type之間的互相轉換。 在性能瓶頸的地方使用寫死的JDBC
- 在對性能要求很嚴格的一些系統中,一些操作(例如批量更新和批量删除)也許直接使用JDBC會更好,但是請先搞清楚這是否是一個瓶頸,并且不要想當然認為JDBC一定會更快。如果确實需要直接使用JDBC,那麼最好打開一個 Hibernate Session 然後從 Session獲得connection,按照這種辦法你仍然可以使用同樣的transaction政策和底層的connection provider。 了解Session清洗( flushing)
- Session會不時的向資料庫同步持久化狀态,如果這種操作進行的過于頻繁,那麼性能會受到一定的影響。有時候你可以通過禁止自動flushing盡量最小化非必要的flushing操作,或者更進一步,在一個特殊transaction中改變查詢和其它操作的順序。 在三層結構中,考慮使用 saveOrUpdate()
- 當使用一個servlet / session bean 的架構的時候, 你可以把已加載的持久對象在session bean層和servlet / JSP 層之間來回傳遞。使用新的session來為每個請求服務,使用 Session.update() 或者Session.saveOrUpdate()來更新對象的持久狀态。 在兩層結構中,記得自己關閉session.
-
當僅僅使用 servlet的時候,你可以在多個客戶請求中複用同一個session,隻是要記得在把控制權交還給用戶端之前disconnect掉session。
為了得到最佳的可伸縮性,資料庫事務(Database Transaction)應該盡可能的短。但是,程式常常需要實作長時間運作的“業務事務(Application Transaction)”,包含一個從使用者的觀點來看的原子操作。這個業務事務可能跨越多次從使用者請求到得到回報的循環。請使用離線對象(Detached Object),或者在兩層結構中,把Hibernate Session從JDBC連接配接中脫離開,下次需要用的時候再連接配接上。絕不要在一個Session中包含多次業務事務,否則你的資料可能會過期失效。
不要把異常看成可恢複的 - 這一點甚至比“最佳實踐”還要重要,這是“必備常識”。當異常發生的時候,復原 Transaction ,關閉Session。如果你不這樣做的話,Hibernate無法保證記憶體狀态精确的反應持久狀态。尤其不要使用Session.load()來判斷一個給定辨別符的對象執行個體在資料庫中是否存在,應該使用find()。當然,有一些例外情況,比如說StaleObjectStateException和ObjectNotFoundException。 對于關聯優先考慮lazy fetching
- 謹慎的使用主動外連接配接抓取(eager (outer-join) fetching)。對于大多數沒有JVM級别緩存的持久對象的關聯,應該使用代理(proxies)或者具有延遲加載屬性的集合(lazy collections)。對于被緩存的對象的關聯,尤其是緩存的命中率非常高的情況下,應該使用outer-join="false",顯式的禁止掉eager fetching。如果那些特殊的确實适合使用outer-join fetch 的場合,請在查詢中使用left join。 考慮把Hibernate代碼從業務邏輯代碼中抽象出來
- 把Hibernate的資料存取代碼隐藏到接口(interface)的後面,組合使用DAO和Thread Local Session模式。通過Hibernate的UserType,你甚至可以用寫死的JDBC來持久化那些本該被Hibernate持久化的類。 (該建議更适用于規模足夠大應用軟體中,對于那些隻有5張表的應用程式并不适合。) 使用與業務有關的鍵值來實作equals()和 hashCode() .
- 如果你在Session外比較對象,你必須要實作equals()和 hashCode()。在Session内部,Java的對象識别可以值得信賴。如果你實作了這些方法,不要再使用資料庫辨識!瞬時對象不具有辨別值,Hibernate會在對象被儲存的時候賦予它一個值。如果對象在被儲存的時候位于Set内,hash code就會變化,要約就被違背。為了實作用與業務有關的鍵值編寫equals()和 hashCode(),你應該使用類屬性的唯一組合。記住,這個鍵值隻是當對象位于Set内部時才需要保證穩定且唯一,并不是在其整個生命周期中都需要(不需要達到資料庫主鍵這樣的穩定性)。絕不要在equals()比較中使用集合(要考慮延遲裝載),這些相關聯的類可能被代理過。 不要用怪異的連接配接映射
- 多對多連接配接用得好的例子實際上相當少見。大多數時候你在“連接配接表”中需要儲存額外的資訊。這種情況下,用兩個指向中介類的一對多的連接配接比較好。實際上,我們認為絕大多數的連接配接是一對多和多對一的,你應該謹慎使用其它連接配接風格,用之前問自己一句,是否真的必須這麼做。