Hibernate本身并不是數(shù)據(jù)庫,它只是一個輕量級的對象-關(guān)系數(shù)據(jù)庫映射(object-relational)工具。它的事務(wù)交由底層的數(shù)據(jù)庫連接管理,如果數(shù)據(jù)庫連接有JTA的支持,那么在Session中進行的操作將是整個原子性JTA事務(wù)的一部分。Hibernate可以看作是添加了面向?qū)ο笳Z義的JDBC瘦適配器(thin adapter)。
SessionFactory的創(chuàng)建需要耗費大量資源,它是線程安全(threadsafe)的對象,在應(yīng)用中它被所有線程共享。而Session的創(chuàng)建耗費資源很少,它不是線程安全的對象,對于一個簡單商業(yè)過程(business process),它應(yīng)該只被使用一次,然后被丟棄。舉例來說,當Hibernate在基于servlet的應(yīng)用中,servlet能夠以下面的方式得到SessionFactory。
SessionFactory sf = (SessionFactory)getServletContext().getAttribute("my.session.factory");
每次調(diào)用SessionFactory的service方法能夠生成一個新的Session對象,然后調(diào)用Session的flush(),調(diào)用commit()提交它的連接,調(diào)用close()關(guān)閉它,最終丟棄它。(SessionFactory可能被保存在JNDI或者一個靜態(tài)的單例(Singleton)輔助變量中。)
在無狀態(tài)的session bean中,可以同樣使用類似的方法。bean在setSessionContext()中得到SessionFactory的實例,每個商業(yè)方法會生成一個Session對象,調(diào)用它的flush()和close(),當然,應(yīng)用不應(yīng)該commit()connection. (把它留給JTA.在容器管理的事務(wù)中,數(shù)據(jù)庫連接會自動完成事務(wù)。)
我們用上述方法使用Hibernate 的Transaction API,對Transaction執(zhí)行一次commit()會把所有狀態(tài)同步,把底層的數(shù)據(jù)庫連接提交(對JTA 事務(wù)會特殊處理。)
這里需要理解flush()的含義。 flush()將持久化存儲與內(nèi)存中的變化進行同步,但不是將內(nèi)存的變化與持久化存儲進行同步。注意對所有的Hibernate JDBD 連接/事務(wù)來說,其隔離級別將施加于所有的Hibernate執(zhí)行的操作之上!
接下來的幾小節(jié)將討論利用版本化的方法來確保事務(wù)原子性,這些“高級”方法需要小心使用。
在創(chuàng)建Hibernate會話(Session)時,你應(yīng)該留意以下的實踐(practices):
對于一個數(shù)據(jù)庫連接,不要創(chuàng)建一個以上的Session或Transaction。
在對于一個數(shù)據(jù)庫連接、一個事務(wù)使用多個Session時,你尤其需要格外地小心。Session對象會記錄下調(diào)入數(shù)據(jù)更新的情況,所以另一個Session對象可能會遇到過時的數(shù)據(jù)。
Session不是線程安全的。決不要在兩個并發(fā)的線程中訪問同一個Session。一個Session一般只對應(yīng)一批需要一次性完成的單元操作!
程序可能在兩批單元操作中并發(fā)訪問同一個對象的持久化狀態(tài)。不管怎樣,持久化類的一個實例不可能在兩個Session中共享。所以有兩種不同的辨別方式:
foo.getId().equals( bar.getId() )
foo==bar
對于依附于某個特定Session的對象,兩種辨別方式是等價的。然而,當程序可能在兩個不同的session中并發(fā)訪問“同一個”(持久化辨別)商業(yè)對象時,兩個實例(對于JVM辨別來說)卻可能是“不同”的。
這種方式把關(guān)于并發(fā)的頭疼問題留給了Hibernate和數(shù)據(jù)庫。程序不需要對任何商業(yè)對象進行同步,只要程序堅持每個Session一個線程,或者對象辨別的策略(在一個Session重,程序可以安全的使用==來比較對象)。
許多商業(yè)過程需要一系列與用戶進行交互的過程,數(shù)據(jù)庫訪問穿插在這些過程中。對于web和企業(yè)應(yīng)用來說,跨一個用戶交互過程的數(shù)據(jù)事務(wù)是不可接受的。
維護各商業(yè)事務(wù)間的隔離(isolocation)就成為應(yīng)用層的部分責任,我們把這種過程稱為長時間運行的應(yīng)用事務(wù)(application transaction)。單一的應(yīng)用事務(wù)可能跨越多個數(shù)據(jù)庫事務(wù)。如果這些數(shù)據(jù)庫事務(wù)中只有一個(最后一個)保存了被修改的數(shù)據(jù),其他事務(wù)只是簡單地讀數(shù)據(jù),則這個應(yīng)用事務(wù)就是原子性的。
唯一滿足高并發(fā)性以及高可擴展性的方法是使用帶有版本化的樂觀并發(fā)控制。Hibernate為使用樂觀并發(fā)控制的代碼提供了三種可能的方法。
在整個商業(yè)過程中使用一個單獨的Session實例以及它的持久化實例,這個Session使用帶有版本化的樂觀鎖定機制,來確保多個數(shù)據(jù)庫事務(wù)對于應(yīng)用來說只是一個邏輯上的事務(wù)。在等待用戶交互時,Session斷開與數(shù)據(jù)庫的連接。這個方法從數(shù)據(jù)庫訪問方面來看是最有效的,應(yīng)用不需要關(guān)心對自己的版本檢查或是重新與不需要序列化(transient)的實例進行關(guān)聯(lián)。
在整個應(yīng)用事務(wù)中,使用單一的Session 實例和它的持久化實例。
Session 使用帶有版本化的樂觀鎖定來保證多個數(shù)據(jù)庫事務(wù)對程序來說就如同是單一的邏輯應(yīng)用事務(wù)。在等待用戶交互的時候,Session 脫離所有的底層JDBC連接。對于數(shù)據(jù)庫訪問來說,這種方法是最高效的。程序自己不需要關(guān)心版本檢查或者把已經(jīng)脫離session的實例重新關(guān)聯(lián)到session。
// foo is an instance loaded earlier by the Sessionsession.reconnect();foo.setProperty("bar");session.flush();session.connection().commit();session.disconnect();
foo對象仍然知道是哪個Session把自己裝載的。 只要Session 擁有一個JDBC連接,我們可以把對象的更改提交。
如果我們的 Session 太大,以至于在用戶思考的時間內(nèi)無法保存住,這種模式就會出現(xiàn)問題。比如,HttpSession應(yīng)該保持盡量小。因為Session也持有(必須的)第一級緩存,包含所有被裝載的對象,我們只能在很少的request/response周期中使用這一策略。這種少用是被鼓勵的,因為Session 很快就會出現(xiàn)過時的數(shù)據(jù)。
每個與持久化存儲的交互出現(xiàn)在一個新的Session中,在每次與數(shù)據(jù)庫的交互中,使用相同的持久化實例。應(yīng)用操作那些從其它Session調(diào)入的已經(jīng)脫離session的實例的狀態(tài),通過使用Session.update()或者Session.saveOrUpdate()來重新建立與它們的關(guān)聯(lián)。
// foo is an instance loaded by a previous Sessionfoo.setProperty("bar");session = factory.openSession();session.saveOrUpdate(foo);session.flush();session.connection().commit();session.close();
你也可以調(diào)用lock()而非update(),如果你確信對象沒有被修改過,可以使用LockMode.READ(進行一次版本檢查,而跳過所有的緩存)。
每當一個新的Session中與數(shù)據(jù)庫出現(xiàn)交互的時候,這個session會在操作持久化實例前重新把它們從數(shù)據(jù)庫中裝載進來。我們現(xiàn)在所說的方式就是你的應(yīng)用程序自己使用版本檢查來確保應(yīng)用事務(wù)的隔離性。(當然,Hibernate仍會為你更新版本號)。從數(shù)據(jù)庫訪問方面來看,這種方法是最沒有效率的,與entity EJB方式類似。
// foo is an instance loaded by a previous Sessionsession = factory.openSession();int oldVersion = foo.getVersion();session.load( foo, foo.getKey() );if ( oldVersion!=foo.getVersion ) throw new StaleObjectStateException();foo.setProperty("bar");session.flush();session.connection().commit();session.close();
當然,如果在低數(shù)據(jù)并行(low-data-concurrency)的環(huán)境中,并不需要版本檢查,你仍可以使用這個方法,只需要忽略版本檢查。
The first approach described above is to maintain a single Session for a whole business process thats spans user think time. (For example, a servlet might keep a Session in the user‘s HttpSession.) For performance reasons you should
上面提到的第一種方法是對于對一個用戶的一次登錄產(chǎn)生的整個商業(yè)過程維護一個Session。(舉例來說,servlet有可能會在用戶的HttpSession中保留一個Session)。為性能考慮,你必須
提交Transaction(或者JDBC連接),然后
(在等待用戶操作前,)斷開Session與JDBC連接。
Session.disconnect()方法會斷開會話與JDBC的連接,把連接返還給連接池(除非是你自己提供這個連接的)。
Session.reconnect()方法會得到一個新的連接(你也可以自己提供一個),重新開始會話。在重新連接后,你可以通過對任何可能被其它事務(wù)更新的對象調(diào)用Session.lock()方法,來強迫對你沒有更新的數(shù)據(jù)進行版本檢查。你不需要對正在更新的數(shù)據(jù)調(diào)用lock()。
這是一個例子:
SessionFactory sessions;List fooList;Bar bar;....Session s = sessions.openSession();Transaction tx = null;try {tx = s.beginTransaction();fooList = s.find("select foo from eg.Foo foo where foo.Date = current date"http:// uses db2 date function);bar = (Bar) s.create(Bar.class);tx.commit();}catch (Exception e) {if (tx!=null) tx.rollback();s.close();throw e;}s.disconnect();
接下來:
s.reconnect();try {tx = s.beginTransaction();bar.setFooTable( new HashMap() );Iterator iter = fooList.iterator();while ( iter.hasNext() ) {Foo foo = (Foo) iter.next();s.lock(foo, LockMode.READ); //check that foo isn‘t stalebar.getFooTable().put( foo.getName(), foo );}tx.commit();}catch (Exception e) {if (tx!=null) tx.rollback();throw e;}finally {s.close();}
從上面的例子可以看到Transaction和Session之間是多對一的關(guān)系。一個Session表示了應(yīng)用程序與數(shù)據(jù)庫之間的一個對話,Transaction把這個對話分隔成一個個在數(shù)據(jù)庫級別具有原子性的單元。
用戶不需要在鎖定策略上花費過多時間,通常我們可以對JDBC連接選定一種隔離級別(isolationn level),然后讓數(shù)據(jù)庫完成所有的工作。高級用戶可能希望得到悲觀鎖定或者在新的事務(wù)開始時重新得到鎖。
Hibernate一直都會使用數(shù)據(jù)庫的鎖定機制,而不會在內(nèi)存中鎖定對象。
LockMode類定義了Hibernate需要的不同的鎖級別。鎖由以下的機制得到:
LockMode.WRITE在Hibernate更新或插入一行數(shù)據(jù)時自動得到。
LockMode.UPGRADE在用戶通過SELECT ... FOR UPDATE這樣的特定請求得到,需要數(shù)據(jù)庫支持這種語法。
LockMode.UPGRADE_NOWAIT在用戶通過SELECT ... FOR UPDATE NOWAIT這樣的特定請求在Oracle數(shù)據(jù)庫環(huán)境下得到。
LockMode.READ在Hibernate在不斷讀(Repeatable Read)和序列化(Serializable)的隔離級別下讀取數(shù)據(jù)時得到。也可以通過用戶的明確請求重新獲得。
LockMode.NONE表示沒有鎖。所有對象在Transaction結(jié)束時會切換到這種鎖模式,通過調(diào)用update()或者saveOrUpdate()與會話進行關(guān)聯(lián)的對象,開始時也會在這種鎖模式。
“明確的用戶請求”會以下的幾種方式出現(xiàn):
調(diào)用Session.load(),指定一種LockMode。
調(diào)用Session.lock()。
調(diào)用Query.setLockMode()。
如果在調(diào)用Session.load()時指定了UPGRADE或者UPGRADE_NOWAIT,并且請求的對象還沒有被會話調(diào)入,那么這個對象會以SELECT ... FOR UPDATE的方式調(diào)入。如果調(diào)用load()在一個已經(jīng)調(diào)入的對象,并且這個對象調(diào)入時的鎖級別沒有請求時來得嚴格,Hibernate會對這個對象調(diào)用lock()。
Session.lock()會執(zhí)行版本號檢查的特定的鎖模式是:READ,UPGRADE或者UPGRADE_NOWAIT。(在UPGRADE或者UPGRADE_NOWAIT,SELECT ... FOR UPGRADE使用的情況下。)
如果數(shù)據(jù)庫不支持所請求的鎖模式,Hibernate將會選擇一種合適的受支持的鎖模式替換(而不是拋出一個異常)。這確保了應(yīng)用具有可移植性。