天天看點

Hibernate最佳實踐與問題集錦

        有人測試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外比較對象,你必須要實作equals()和 hashCode()。在Session内部,Java的對象識别可以值得信賴。如果你實作了這些方法,不要再使用資料庫辨識!瞬時對象不具有辨別值,Hibernate會在對象被儲存的時候賦予它一個值。如果對象在被儲存的時候位于Set内,hash code就會變化,要約就被違背。為了實作用與業務有關的鍵值編寫equals()和 hashCode(),你應該使用類屬性的唯一組合。記住,這個鍵值隻是當對象位于Set内部時才需要保證穩定且唯一,并不是在其整個生命周期中都需要(不需要達到資料庫主鍵這樣的穩定性)。絕不要在equals()比較中使用集合(要考慮延遲裝載),這些相關聯的類可能被代理過。
 Hibernate下資料批量處了解決方案

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();

  那麼,關于怎樣删除和更新資料呢?那好,在Hibernate2.1.6或者更後版本,scroll() 這個方法将是最好的途徑:

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();

  這種做法并不困難,也不算不優雅。請注意,如果Customer啟用了second-level caching ,我們仍然會有一些記憶體管理的問題。原因就是對于使用者的每一次插入和更新,Hibernate在事務處理結束後不得不通告second-level cache 。是以,我們在批處理情況下将要禁用使用者使用緩存。

第 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()比較中使用集合(要考慮延遲裝載),這些相關聯的類可能被代理過。
不要用怪異的連接配接映射
多對多連接配接用得好的例子實際上相當少見。大多數時候你在“連接配接表”中需要儲存額外的資訊。這種情況下,用兩個指向中介類的一對多的連接配接比較好。實際上,我們認為絕大多數的連接配接是一對多和多對一的,你應該謹慎使用其它連接配接風格,用之前問自己一句,是否真的必須這麼做。