Hibernate----自定義數據類型
package org.william.First;
import javax.sql.*; /** ?/* (non-Javadoc) |
posted @ 2006-08-22 10:51 Binary 閱讀(411) | 評論 (0) | 編輯 收藏
VincentVicent's blog
隨筆 - 74, 文章 - 0, 評論 - 5, 引用 - 0
|
Hibernate----自定義數據類型
posted @ 2006-08-22 10:51 Binary 閱讀(411) | 評論 (0) | 編輯 收藏 Hibernate的事務和并發(ZT)
Hibernate的事務和并發控制很容易掌握。Hibernate直接使用JDBC連接和JTA資源,不添加任何附加鎖定行為。我們強烈推薦你花點時間了解JDBC編程,ANSI SQL查詢語言和你使用的數據庫系統的事務隔離規范。Hibernate只添加自動版本管理,而不會鎖 定內存中的對象,也不會改變數據庫事務的隔離級別。基本上,使用 Hibernate就好像直接使用JDBC(或者JTA/CMT)來訪問你的數據庫資源。
?除了自動版本管理,針對行級悲觀鎖定,Hibernate也提供了輔助的API,它使用了 SELECT FOR UPDATE的SQL語法。本章后面會討論這個API。 我們從Configuration層、SessionFactory層, 和 Session層開始討論Hibernate的并行控制、數據庫事務和應用 程序的長事務。 12.1.Session和事務范圍(transaction scopes) 一個SessionFactory對象的創建代價很昂貴,它是線程安全的對象,它被設計成可以 為所有的應用程序線程所共享。它只創建一次,通常是在應用程序啟動的時候,由一個 Configuraion的實例來創建。 一個Session的對象是輕型的,非線程安全的,對于單個業務進程,單個的 工作單元而言,它只被使用一次,然后就丟棄。只有在需要的時候,Session 才會獲取一個JDBC的Connection(或一個Datasource)對象。所以你可以放心的打開和關閉Session,甚至當你并不確定一個特定的請求是否需要數據訪問時,你也可以這樣做。(一旦你實現下面提到的使用了請求攔截的模式,這就 變得很重要了。 此外我們還要考慮數據庫事務。數據庫事務應該盡可能的短,降低數據庫鎖定造成的資源爭用。 數據庫長事務會導致你的應用程序無法擴展到高的并發負載。 一個操作單元(Unit of work)的范圍是多大?單個的Hibernate Session能跨越多個 數據庫事務嗎?還是一個Session的作用范圍對應一個數據庫事務的范圍?應該何時打開 Session,何時關閉Session?,你又如何劃分數據庫事務的邊界呢? 12.1.1.操作單元(Unit of work) 首先,別再用session-per-operation這種反模式了,也就是說,在單個線程中, 不要因為一次簡單的數據庫調用,就打開和關閉一次Session!數據庫事務也是如此。 應用程序中的數據庫調用是按照計劃好的次序,分組為原子的操作單元。(注意,這也意味著,應用程序中,在單個的SQL語句發送之后,自動事務提交(auto-commit)模式失效了。這種模式專門為SQL控制臺操作設計的。 Hibernate禁止立即自動事務提交模式,或者期望應用服務器禁止立即自動事務提交模式。) 在多用戶的client/server應用程序中,最常用的模式是 每個請求一個會話(session-per-request)。在這種模式下,來自客戶端的請求被發送到服務器端(即Hibernate持久化層運行的地方),一 個新的Hibernate Session被打開,并且執行這個操作單元中所有的數據庫操作。 一旦操作完成(同時發送到客戶端的響應也準備就緒),session被同步,然后關閉。你也可以使用單 個數據庫事務來處理客戶端請求,在你打開Session之后啟動事務,在你關閉 Session之前提交事務。會話和請求之間的關系是一對一的關系,這種模式對 于大多數應用程序來說是很棒的。 真正的挑戰在于如何去實現這種模式:不僅Session和事務必須被正確的開始和結束,而且他們也必須能被數據訪問操作訪問。用攔截器來實現操作單元的劃分,該攔截器在客戶端請求達到服務器端的時候開始,在服務器端發送響應(即,ServletFilter)之前結束。我們推薦 使用一個ThreadLocal 變量,把 Session綁定到處理客戶端請求的線 程上去。這種方式可以讓運行在該線程上的所有程序代碼輕松的訪問Session(就像訪問一個靜態變量那樣)。你也可以在一個ThreadLocal 變量中保持事務上下文環境,不過這依賴 于你所選擇的數據庫事務劃分機制。這種實現模式被稱之為 ThreadLocal Session和 Open Session in View。你可以很容易的擴展本文前面章節展示的 HibernateUtil 輔助類來實現這種模式。當然,你必胝業揭恢質迪擲菇仄韉姆椒ǎ?且可以把攔截器集成到你的應用環境中。請參考Hibernate網站上面的提示和例子。 12.1.2.應用程序事務(Application transactions) session-per-request模式不僅僅是一個可以用來設計操作單元的有用概念。很多業務處理流程都需 要一系列完整的和用戶之間的交互,即用戶對數據庫的交叉訪問。在基于web的應用和企業 應用中,跨用戶交互的數據庫事務是無法接受的。考慮下面的例子: 在界面的第一屏,打開對話框,用戶所看到的數據是被一個特定的 Session 和數據 庫事務載入(load)的。用戶可以隨意修改對話框中的數據對象。 5分鐘后,用戶點擊“保存”,期望所做出的修改被持久化;同時他也期望自己是唯一修改這個信息的人,不會出現 修改沖突。 從用戶的角度來看,我們把這個操作單元稱為應用程序長事務(application transaction)。 在你的應用程序中,可以有很多種方法來實現它。 頭一個幼稚的做法是,在用戶思考的過程中,保持Session和數據庫事務是打開的, 保持數據庫鎖定,以阻止并發修改,從而保證數據庫事務隔離級別和原子操作。這種方式當然是一個反模式, 因為數據庫鎖定的維持會導致應用程序無法擴展并發用戶的數目。 很明顯,我們必須使用多個數據庫事務來實現一個應用程序事務。在這個例子中,維護業務處理流程的 事務隔離變成了應用程序層的部分責任。單個應用程序事務通常跨越多個數據庫事務。如果僅僅只有一 個數據庫事務(最后的那個事務)保存更新過的數據,而所有其他事務只是單純的讀取數據(例如在一 個跨越多個請求/響應周期的向導風格的對話框中),那么應用程序事務將保證其原子性。這種方式比聽 起來還要容易實現,特別是當你使用了Hibernate的下述特性的時候: 自動版本化 - Hibernate能夠自動進行樂觀并發控制 ,如果在用戶思考 的過程中發生并發修改沖突,Hibernate能夠自動檢測到。 脫管對象(Detached Objects)- 如果你決定采用前面已經討論過的 session-per-request模式,所有載入的實例在用戶思考的過程中都處于與Session脫離的狀態。Hibernate允許你把與Session脫離的對象重新關聯到Session 上,并且對修改進行持久化,這種模式被稱為 session-per-request-with-detached-objects。自動版本化被用來隔離并發修改。 長生命周期的Session (Long Session)- Hibernate 的Session 可以在數據庫事務提交之后和底層的JDBC連接斷開,當一個新的客戶端請求到來的時候,它又重新連接上底層的 JDBC連接。這種模式被稱之為session-per-application-transaction,這種情況可能會造成不必要的Session和JDBC連接的重新關聯。自動版本化被用來隔離并發修改。 session-per-request-with-detached-objects 和 session-per-application-transaction 各有優缺點,我們在本章后面樂觀并發 控制那部分再進行討論。 12.1.3.關注對象標識(Considering object identity) 應用程序可能在兩個不同的Session中并發訪問同一持久化狀態,但是, 一個持久化類的實例無法在兩個 Session中共享。因此有兩種不同的標識語義: 數據庫標識 foo.getId().equals( bar.getId() ) JVM 標識 foo==bar 對于那些關聯到 特定Session (也就是在單個Session的范圍內)上的對象來說,這 兩種標識的語義是等價的,與數據庫標識對應的JVM標識是由Hibernate來保 證的。不過,當應用程序在兩個不同的session中并發訪問具有同一持久化標識的業務對象實例的時候,這個業務對象的兩個實例事實上是不相同的(從 JVM識別來看)。這種沖突可以通過在同步和提交的時候使用自動版本化和樂觀鎖定方法來解決。 這種方式把關于并發的頭疼問題留給了Hibernate和數據庫;由于在單個線程內,操作單元中的對象識別不 需要代價昂貴的鎖定或其他意義上的同步,因此它同時可以提供最好的可伸縮性。只要在單個線程只持有一個 Session,應用程序就不需要同步任何業務對象。在Session 的范圍內,應用程序可以放心的使用==進行對象比較。 不過,應用程序在Session的外面使用==進行對象比較可能會 導致無法預期的結果。在一些無法預料的場合,例如,如果你把兩個脫管對象實例放進同一個 Set的時候,就可能發生。這兩個對象實例可能有同一個數據庫標識(也就是說, 他們代表了表的同一行數據),從JVM標識的定義上來說,對脫管的對象而言,Hibernate無法保證他們的的JVM標識一致。開發人員必須覆蓋持久化類的equals()方法和 hashCode() 方法,從而實現自定義的對象相等語義。警告:不要使用數據庫標識 來實現對象相等,應該使用業務鍵值,由唯一的,通常不變的屬性組成。當一個瞬時對象被持久化的時 候,它的數據庫標識會發生改變。如果一個瞬時對象(通常也包括脫管對象實例)被放入一 個Set,改變它的hashcode會導致與這個Set的關系中斷。雖 然業務鍵值的屬性不象數據庫主鍵那樣穩定不變,但是你只需要保證在同一個Set 中的對象屬性的穩定性就足夠了。請到Hibernate網站去尋求這個問題更多的詳細的討論。請注意,這不是一 個有關Hibernate的問題,而僅僅是一個關于Java對象標識和判等行為如何實現的問題。 12.1.4.常見問題 決不要使用反模式session-per-user-session或者 session-per-application(當然,這個規定幾乎沒有例外)。請注意,下述一些問題可能也會出現在我們推薦的模式中,在你作出某個設計決定之前,請務必理解該模式的應用前提。 Session 是一個非線程安全的類。如果一個Session 實例允許共享的話,那些支持并發運行的東東,例如HTTP request,session beans,或者是 Swing workers,將會導致出現資源爭用(race condition)。如果在HttpSession中有 Hibernate 的Session的話(稍后討論),你應該考慮同步訪問你的Http session。否則,只要用戶足夠快的點擊瀏覽器的“刷新”,就會導致兩個并發運行線程使用同一個 Session。 一個由Hibernate拋出的異常意味著你必須立即回滾數據庫事務,并立即關閉Session (稍后會展開討論)。如果你的Session綁定到一個應用程序上,你必 須停止該應用程序。回滾數據庫事務并不會把你的業務對象退回到事務啟動時候的狀態。這 意味著數據庫狀態和業務對象狀態不同步。通常情況下,這不是什么問題,因為異常是不可 恢復的,你必須在回滾之后重新開始執行。 Session 緩存了處于持久化狀態的每個對象(Hibernate會監視和檢查臟數據)。這意味著,如果你讓Session打開很長一段時間,或是僅僅載入了過多的數據, Session占用的內存會一直增長,直到拋出OutOfMemoryException異常。這個 問題的一個解決方法是調用clear() 和evict()來管理 Session的緩存,但是如果你需要大批量數據操作的話,最好考慮 使用存儲過程。在第14章 批量處理(Batch processing)中有一些解決方案。在用戶會話期間一直保持 Session打開也意味著出現臟數據的可能性很高。 12.2.數據庫事務聲明 數據庫(或者系統)事務的聲明總是必須的。在數據庫事務之外,就無法和數據庫通訊(這可能會讓那些習慣于 自動提交事務模式的開發人員感到迷惑)。永遠使用清晰的事務聲明,即使只讀操作也是如此。進行 顯式的事務聲明并不總是需要的,這取決于你的事務隔離級別和數據庫的能力,但不管怎么說,聲明事務總歸有益無害。 一個Hibernate應用程序可以運行在非托管環境中(也就是獨立運行的應用程序,簡單Web應用程序, 或者Swing圖形桌面應用程序),也可以運行在托管的J2EE環境中。在一個非托管環境中,Hibernate 通常自己負責管理數據庫連接池。應用程序開發人員必須手工設置事務聲明,換句話說,就是手工啟 動,提交,或者回滾數據庫事務。一個托管的環境通常提供了容器管理事務,例如事務裝配通過可聲 明的方式定義在EJB session beans的部署描述符中。可編程式事務聲明不再需要,即使是 Session 的同步也可以自動完成。 讓持久層具備可移植性是人們的理想。Hibernate提供了一套稱為Transaction的封裝API,用來把你的部署環境中的本地事務管理系統轉換到Hibernate事務上。這個API是可選的,但是我們強烈 推薦你使用,除非你用CMT session bean。 通常情況下,結束 Session 包含了四個不同的階段: 同步session(flush,刷出到磁盤) 提交事務 關閉session 處理異常 session的同步(flush,刷出)前面已經討論過了,我們現在進一步考察在托管和非托管環境下的事務聲明和異常處理。 12.2.1.非托管環境 如果Hibernat持久層運行在一個非托管環境中,數據庫連接通常由Hibernate的連接池機制 來處理。
你不需要顯式flush() Session - 對commit()的調用會自動觸發session的同步。 調用 close() 標志session的結束。 close()方法重要的暗示是,session釋放了JDBC連接。 這段Java代碼是可移植的,可以在非托管環境和JTA環境中運行。 你很可能從未在一個標準的應用程序的業務代碼中見過這樣的用法;致命的(系統)異常應該總是在應用程序“頂層”被捕獲。換句話說,執行Hibernate調用的代碼(在持久層)和處理 RuntimeException異常的代碼(通常只能清理和退出應用程序)應該在不同的應用程序邏輯層。這對于你設計自己的軟件系統來說是一個挑戰,只要有可能,你就應該使用 J2EE/EJB容器服務。異常處理將在本章稍后進行討論。 請注意,你應該選擇 org.hibernate.transaction.JDBCTransactionFactory (這是默認選項). 12.2.2.使用JTA 如果你的持久層運行在一個應用服務器中(例如,在EJB session beans的后面),Hibernate獲取 的每個數據源連接將自動成為全局JTA事務的一部分。Hibernate提供了兩種策略進行JTA集成。 如果你使用bean管理事務(BMT),可以通過使用Hibernate的 Transaction API來告訴 應用服務器啟動和結束BMT事務。因此,事務管理代碼和在非托管環境下是一樣的。
請注意,當你配置Hibernate事務工廠的時候,在一個BMT session bean中,你應該選擇 org.hibernate.transaction.JTATransactionFactory,在一個 CMT session bean中選擇org.hibernate.transaction.CMTTransactionFactory。記住,同時也要設置org.hibernate.transaction.manager_lookup_class。 如果你使用CMT環境,并且讓容器自動同步和關閉session,你可能也希望在你代碼的不同部分使用同一個session。一般來說,在一個非托管環境中,你可以使用一個ThreadLocal 變量來持有這個session,但是單個EJB方法調用可能會在不同的線程中執行(舉例來說,一個session bean調用另一個session bean)。如果你不想在應用代碼中被傳遞Session對 象實例的問題困擾的話,那么SessionFactory 提供的 getCurrentSession()方法就很適合你,該方法返回一個綁定到JTA事務上下文環境中的session實例。這也是把Hibernate集成到一個應用程序中的最簡單的方法!這個“當前的”session總是可以自動同步和自動關閉(不考慮上述的屬性設置)。我們的session/transaction 管理代碼減少到如下所示:
換句話來說,在一個托管環境下,你要做的所有的事情就是調用 SessionFactory.getCurrentSession(),然后進行你的數據訪問,把其余的工作交給容器來做。事務在你的session bean的部署描述符中以可聲明的方式來設置。session的生命周期完全 由Hibernate來管理。 對after_statement連接釋放方式有一個警告。因為JTA規范的一個很愚蠢的限制,Hibernate不可能自動清理任何未關閉的 ScrollableResults 或者Iterator,它們是由scroll()或iterate()產生的。你must通過在finally塊中,顯式調用 ScrollableResults.close()或者Hibernate.close(Iterator)方法來釋放底層數據庫游標。(當然,大部分程序完全可以很容易的避免在CMT代碼中出現scroll()或iterate()。) 12.2.3.異常處理 如果 Session 拋出異常 (包括任何SQLException), 你應該立即回滾數據庫事務,調用 Session.close() ,丟棄該 Session實例。Session的某些方法可能會導致session 處于不一致的狀態。所有由Hibernate拋出的異常都視為不可以恢復的。確保在 finally 代碼塊中調用close()方法,以關閉掉 Session。 HibernateException是一個非檢查期異常(這不同于Hibernate老的版本),它封裝了Hibernate持久層可能出現的大多數錯誤。我們的觀點是,不應該強迫應用程序開發人員在底層捕獲無法恢復的異常。在大多數軟件系統中,非檢查期異常和致命異常都是在相應方法調用的堆棧的頂層被處理的(也就是說,在軟件上面的邏輯層),并且提供一個錯誤信息給應用軟件的用戶(或者采取其他某些相應的操作)。請注意,Hibernate也有可能拋出其他并不屬于 HibernateException的非檢查期異常。這些異常同樣也是無法恢復的,應該 采取某些相應的操作去處理。 在和數據庫進行交互時,Hibernate把捕獲的SQLException封裝為Hibernate的 JDBCException。事實上,Hibernate嘗試把異常轉換為更有實際含義的JDBCException異常的子類。底層的SQLException可以通過JDBCException.getCause()來得到。Hibernate通過使用關聯到 SessionFactory上的SQLExceptionConverter來把SQLException轉換為一個對應的JDBCException 異常的子類。默認情況下,SQLExceptionConverter可以通過配置dialect 選項指定;此外,也可以使用用戶自定義的實現類(參考javadocs SQLExceptionConverterFactory類來了解詳情)。標準的 JDBCException子類型是: JDBCConnectionException - 指明底層的JDBC通訊出現錯誤 SQLGrammarException - 指明發送的SQL語句的語法或者格式錯誤 ConstraintViolationException - 指明某種類型的約束違例錯誤 LockAcquisitionException - 指明了在執行請求操作時,獲取 所需的鎖級別時出現的錯誤。 GenericJDBCException - 不屬于任何其他種類的原生異常 12.3.樂觀并發控制(Optimistic concurrency control) 唯一能夠同時保持高并發和高可伸縮性的方法就是使用帶版本化的樂觀并發控制。版本檢查使用版本號、或者時間戳來檢測更新沖突(并且防止更新丟失)。Hibernate為使用樂觀并發控制的代碼提供了三種可能的方法,應用程序在編寫這些代碼時,可以采用它們。我們已經在前面應用程序長事務那部分展示了 樂觀并發控制的應用場景,此外,在單個數據庫事務范圍內,版本檢查也提供了防止更新丟失的好處。 12.3.1.應用程序級別的版本檢查(Application version checking) 未能充分利用Hibernate功能的實現代碼中,每次和數據庫交互都需要一個新的 Session,而且開發人員必須在顯示數據之前從數據庫中重 新載入所有的持久化對象實例。這種方式迫使應用程序自己實現版本檢查來確保 應用程序事務的隔離,從數據訪問的角度來說是最低效的。這種使用方式和 entity EJB最相似。 // foo is an instance loaded by a previous Session session = factory.openSession(); Transaction t = session.beginTransaction(); int oldVersion = foo.getVersion(); session.load( foo, foo.getKey() ); // load the current state if ( oldVersion!=foo.getVersion ) throw new StaleObjectStateException(); foo.setProperty("bar"); t.commit(); session.close(); version 屬性使用 來映射,如果對象 是臟數據,在同步的時候,Hibernate會自動增加版本號。 當然,如果你的應用是在一個低數據并發環境下,并不需要版本檢查的話,你照樣可以使用 這種方式,只不過跳過版本檢查就是了。在這種情況下,最晚提交生效(last commit wins)就是你的應用程序長事務的默認處理策略。請記住這種策略可能會讓應用軟件的用戶感到困惑,因為他們有可能會碰上更新丟失掉卻沒 有出錯信息,或者需要合并更改沖突的情況。 很明顯,手工進行版本檢查只適合于某些軟件規模非常小的應用場景,對于大多數軟件應用場景來說并不現實。通常情況下,不僅是單個對象實例需要進行版本檢查,整個被修改過的關聯對象圖也都需要進行版本檢查。作為標準設計范例,Hibernate使用長生命周期 Session的方式,或者脫管對象實例的方式來提供自動版本檢查。 12.3.2.長生命周期session和自動版本化 單個 Session實例和它所關聯的所有持久化對象實例都被用于整個應用程序事務。Hibernate在同步的時候進行對象實例的版本檢查,如果檢測到并發修改則拋出異常。由開發人員來決定是否需要捕獲和處理這個異常(通常的抉擇是給用戶 提供一個合并更改,或者在無臟數據情況下重新進行業務操作的機會)。 在等待用戶交互的時候, Session 斷開底層的JDBC連接。這種方式 以數據庫訪問的角度來說是最高效的方式。應用程序不需要關心版本檢查或脫管對象實例 的重新關聯,在每個數據庫事務中,應用程序也不需要載入讀取對象實例。
foo 對象始終和載入它的Session相關聯。 Session.reconnect()獲取一個新的數據庫連接(或者 你可以提供一個),并且繼續當前的session。Session.disconnect() 方法把session與JDBC連接斷開,把數據庫連接返回到連接池(除非是你自己提供的數據 庫連接)。在Session重新連接上數據庫連接之后,你可以對任何可能被其他事務更新過 的對象調用Session.lock(),設置LockMode.READ 鎖定模式,這樣你就可以對那些你不準備更新的數據進行強制版本檢查。此外,你并不需要 鎖定那些你準備更新的數據。 假若對disconnect()和reconnect()的顯式調用發生得太頻繁了,你可以使用hibernate.connection.release_mode來代替。 如果在用戶思考的過程中,Session因為太大了而不能保存,那么這種模式是有 問題的。舉例來說,一個HttpSession應該盡可能的小。由于 Session是一級緩存,并且保持了所有被載入過的對象,因此我們只應該在那些少量的request/response情況下使用這種策略。而且在這種情況下, Session 里面很快就會有臟數據出現,因此請牢牢記住這一建議。 此外,也請注意,你應該讓與數據庫連接斷開的Session對持久層保持 關閉狀態。換句話說,使用有狀態的EJB session bean來持有Session, 而不要把它傳遞到web層(甚至把它序列化到一個單獨的層),保存在HttpSession中。 12.3.3.脫管對象(deatched object)和自動版本化 這種方式下,與持久化存儲的每次交互都發生在一個新的Session中。 然而,同一持久化對象實例可以在多次與數據庫的交互中重用。應用程序操縱脫管對象實例 的狀態,這個脫管對象實例最初是在另一個Session 中載入的,然后 調用 Session.update(),Session.saveOrUpdate(), 或者 Session.merge() 來重新關聯該對象實例。
Hibernate會再一次在同步的時候檢查對象實例的版本,如果發生更新沖突,就拋出異常。 如果你確信對象沒有被修改過,你也可以調用lock() 來設置 LockMode.READ(繞過所有的緩存,執行版本檢查),從而取 代 update()操作。 12.3.4.定制自動版本化行為 對于特定的屬性和集合,通過為它們設置映射屬性optimistic-lock的值 為false,來禁止Hibernate的版本自動增加。這樣的話,如果該屬性 臟數據,Hibernate將不再增加版本號。 遺留系統的數據庫Schema通常是靜態的,不可修改的。或者,其他應用程序也可能訪問同一數據 庫,根本無法得知如何處理版本號,甚至時間戳。在以上的所有場景中,實現版本化不能依靠 數據庫表的某個特定列。在的映射中設置 optimistic-lock="all"可以在沒有版本或者時間戳屬性映射的情況下實現版本檢查,此時Hibernate將比較一行記錄的每個字段的狀態。請注意,只有當Hibernate能夠比較新舊狀態的情況下,這種方式才能生效,也就是說, 你必須使用單個長生命周期Session模式,而不能使用 session-per-request-with-detached-objects模式。 有些情況下,只要更改不發生交錯,并發修改也是允許的。當你在 的映射中設置optimistic-lock="dirty",Hibernate在同步的時候將只比較有臟 數據的字段。 在以上所有場景中,不管是專門設置一個版本/時間戳列,還是進行全部字段/臟數據字段比較, Hibernate都會針對每個實體對象發送一條UPDATE(帶有相應的 WHERE語句)的SQL語句來執行版本檢查和數據更新。如果你對關聯實體 設置級聯關系使用傳播性持久化(transitive persistence),那么Hibernate可能會執行不必 要的update語句。這通常不是個問題,但是數據庫里面對on update點火 的觸發器可能在脫管對象沒有任何更改的情況下被觸發。因此,你可以在的映射中,通過設置select-before-update="true" 來定制這一行為,強制Hibernate SELECT這個對象實例,從而保證, 在更新記錄之前,對象的確是被修改過。 12.4.悲觀鎖定(Pessimistic Locking) 用戶其實并不需要花很多精力去擔心鎖定策略的問題。通常情況下,只要為JDBC連接指定一下隔 離級別,然后讓數據庫去搞定一切就夠了。然而,高級用戶有時候希望進行一個排它的悲觀鎖定, 或者在一個新的事務啟動的時候,重新進行鎖定。 Hibernate總是使用數據庫的鎖定機制,從不在內存中鎖定對象! 類LockMode 定義了Hibernate所需的不同的鎖定級別。一個鎖定 可以通過以下的機制來設置: 當Hibernate更新或者插入一行記錄的時候,鎖定級別自動設置為LockMode.WRITE。 當用戶顯式的使用數據庫支持的SQL格式SELECT ... FOR UPDATE 發送SQL的時候,鎖定級別設置為LockMode.UPGRADE 當用戶顯式的使用Oracle數據庫的SQL語句SELECT ... FOR UPDATE NOWAIT 的時候,鎖定級別設置LockMode.UPGRADE_NOWAIT 當Hibernate在“可重復讀”或者是“序列化”數據庫隔離級別下讀取數據的時候,鎖定模式 自動設置為LockMode.READ。這種模式也可以通過用戶顯式指定進行設置。 LockMode.NONE 代表無需鎖定。在Transaction結束時, 所有的對象都切換到該模式上來。與session相關聯的對象通過調用update() 或者saveOrUpdate()脫離該模式。 "顯式的用戶指定"可以通過以下幾種方式之一來表示: 調用 Session.load()的時候指定鎖定模式(LockMode)。 調用Session.lock()。 調用Query.setLockMode()。 如果在UPGRADE或者UPGRADE_NOWAIT鎖定模式下調用Session.load(),并且要讀取的對象尚未被session載入過,那么對象 通過SELECT ... FOR UPDATE這樣的SQL語句被載入。如果為一個對象調用 load()方法時,該對象已經在另一個較少限制的鎖定模式下被載入了,那么Hibernate就對該對象調用lock() 方法。 如果指定的鎖定模式是READ, UPGRADE 或 UPGRADE_NOWAIT,那么Session.lock()就 執行版本號檢查。(在UPGRADE 或者UPGRADE_NOWAIT 鎖定模式下,執行SELECT ... FOR UPDATE這樣的SQL語句。) 如果數據庫不支持用戶設置的鎖定模式,Hibernate將使用適當的替代模式(而不是扔出異常)。 這一點可以確保應用程序的可移植性。 posted @ 2006-08-22 10:50 Binary 閱讀(198) | 評論 (0) | 編輯 收藏 Apache Commons Chain簡明手冊
摘要: 基本對象
1.
?
Command
接口。它是
Commons Chain
中... 閱讀全文
posted @ 2006-08-22 10:47 Binary 閱讀(2157) | 評論 (0) | 編輯 收藏 Java1.5語言新特性簡單總結posted @ 2006-08-22 10:45 Binary 閱讀(173) | 評論 (0) | 編輯 收藏 J2SE5.0新特性之ProcessBuilder
這個例子使用了J2SE5.0的ProcessBuilder類執行外部的程序,相對于?Runtime.exec?,它更方便,可以設置環境變量等。這里使用它在windows下讀取物理網卡的地址
posted @ 2006-08-22 10:45 Binary 閱讀(125) | 評論 (0) | 編輯 收藏 InvocationHandler 操作例子(切面概念)
三個類,一個接口,不費話:
public interface IHello { public void hello(String name); } -------------------------------------------------------------------------------- public class HelloSpeaker implements IHello { public void hello(String name) { System.out.println("Hello, " + name); } } -------------------------------------------------------------------------------- import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.util.logging.Level; import java.util.logging.Logger; public class LogHandler implements InvocationHandler { private Object delegate; private Logger logger = Logger.getLogger(this.getClass().getName()); public Object bind(Object delegate) { this.delegate = delegate; return Proxy.newProxyInstance(delegate.getClass().getClassLoader(), delegate.getClass().getInterfaces(), this); } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result = null; try { log("method starts..." + method); result = method.invoke(delegate, args); logger.log(Level.INFO, "method ends..." + method); } catch (Exception e) { log(e.toString()); } return result; } private void log(String message) { logger.log(Level.INFO, message); } } -------------------------------------------------------------------------------- public class ProxyDemo { public static void main(String[] args) { LogHandler logHandler = new LogHandler(); IHello helloProxy = (IHello) logHandler.bind(new HelloSpeaker()); helloProxy.hello("Justin"); } 』 好了,拷貝上面的自己運行看看就知道了,原來切面就是這么回事。 posted @ 2006-08-22 10:43 Binary 閱讀(208) | 評論 (0) | 編輯 收藏 開始使用Commons Chain (第一部分)
作為程序開發人員,我們經常需要對一個實際上程序性的系統應用面向對象的方法。商業分析家和管理人員描述這樣的系統時通常不使用類層次和序列圖,而是使用流程圖和工作流圖表。但是不論如何,使用面向對象的方法解決這些問題時會帶來更多的靈活性。面向對象的設計模式提供了有用的結構和行為來描述這種順序的處理,比如模版方法(Template Method)[GoF]和責任鏈(Chain of Responsibility)[GoF]。
Jakarta Commons的子項目Chain將上述兩個模式組合成一個可復用的Java框架用于描述順序的處理流程。這個在Jakarta Commons project社區中開發的框架,已經被廣泛的接受并且使用于許多有趣的應用中,特別的是他被Struts和Shale應用框架作為處理HTTP請求處理的基礎機制。你可以在需要定義和執行一組連續的步驟時使用Commons Chain。 至于經典設計模式,開發者和架構師普遍使用模版方法(Template Method)造型順序處理。模版方法(Template Method)中使用一個抽象的父類定義使用的算法:處理的步驟,具體實現交給子類。當然,父類也可以為算法所使用的方法提供一個缺省實現。 由于模版方法(Template Method)依賴繼承——子類必須繼承定義了算法的父類——因此使用這個模式的軟件表現出緊耦合而且缺少靈活性。又由于實現類添加自己的行為前必須擴展父類,溝每⑷嗽北幌拗樸誒嗖憒沃校傭拗屏順絳蟶杓頻牧榛钚浴ommons Chain使用配置文件定義算法,在程序運行時解析配置文件,從而很好的解決了這個問題。 現在來看一下Commons Chain是怎樣工作的,我們從一個人造的例子開始:二手車銷售員的商業流程。下面是銷售流程的步驟: 1.????????得到用戶信息 2.????????試車 3.????????談判銷售 4.????????安排財務 5.????????結束銷售 現在假設使用模版方法(Template Method)造型這個流程。首先建立一個定義了算法的抽象類: 清單1 public abstract class SellVehicleTemplate { 現在來看一下怎樣用Commons Chain實現這個流程。首先,下載Commons Chain。你可以直接下載最新的zip或tar文件,也可以從CVS或者SubVersion源碼庫檢出Commons Chain模塊得到最新的代碼。解壓縮打包文件,將commons-chain.jar放入你的classpath中。 使用Commons Chain實現這個商業流程,必須將流程中的每一步寫成一個類,這個類需要有一個public的方法execute()。這和傳統的命令模式(Command pattern)實現相同。下面簡單實現了“得到用戶信息”: 清單2 package com.jadecove.chain.sample; 由于只是演示,這個類并沒有做很多工作。這里將用戶名放入了Context對象ctx中。這個Context對象連接了各個命令。暫時先將這個對象想象成根據關鍵字存取值的哈希表。所有后來的命令可以通過它訪問剛才放入的用戶名。TestDriveVehicle,NegotiateSale和 ArrangeFinancing命令的實現只是簡單的打印了將執行什么操作。 清單3 package com.jadecove.chain.sample; CloseSale從Context對象中取出GetCustomerInfo放入的用戶名,并將其打印。 清單4 package com.jadecove.chain.sample; 現在你可以將這個流程定義成一個序列(或者說“命令鏈”)。 清單5 package com.jadecove.chain.sample; 運行這個類將會輸出以下結果: Get customer info Test drive the vehicle Negotiate sale Arrange financing Congratulations George Burdell, you bought a new car! 在進一步深入之前,讓我們來看一下我們使用了的Commons Chain的類和接口。 ![]() Command 類和Chain類的關系就是組合模式(Composite pattern)[GoF]的例子:Chain不僅由多個Command組成,而且自己也是Command。這使你可以非常簡單得將單個命令(Command)替換成由多個命令(Command)組成的鏈(Chain)。這個由Command對象唯一操作定義的方法代表了一個直接的命令: public boolean execute(Context context); 參數context僅僅是一個存放了名稱-值對的集合。接口Context在這里作為一個標記接口:它擴展了java.util.Map但是沒有添加任何特殊的行為。于此相反,類ContextBase不僅提供了對Map的實現而且增加了一個特性:屬性-域透明。這個特性可以通過使用Map的put和get 方法操作JavaBean的域,當然這些域必須使用標準的getFoo和setFoo方法定義。那些通過JavaBean的“setter”方法設置的值,可以通過對應的域名稱,用Map的get方法得到。同樣,那些用Map的put方法設置的值可以通過JavaBean的“getter”方法得到。 例如,我們可以創建一個專門的context提供顯式的customerName屬性支持。 清單6 package com.jadecove.chain.sample; 現在你既可以進行Map的一般屬性存取操作同時也可以使用顯式的JavaBean的訪問和修改域的方法,這兩個將產生同樣的效果。但是首先你需要在運行SellVehicleChain時實例化SellVehiceContext而不是ContextBase。 清單7 public static void main(String[] args) throws Exception { 盡管你不改變GetCustomerInfo中存放用戶名的方法——仍然使用ctx.put("customerName", "George Burdell")——你可以在CloseSale中使用getCustomerName()方法得到用戶名。 清單8 ????????public boolean execute(Context ctx) throws Exception { 那些依賴類型安全和context的顯式域的命令(Command)可以利用標準的getter和setter方法。當一些新的命令(Command)被添加時,它們可以不用考慮context的具體實現,直接通過Map的get和put操作屬性。不論采用何種機制,ContextBase類都可以保證命令(Command)間可以通過context互操作。 下面這個例子展示了如何使用Commons Chain的API建立并執行一組順序的命令。當然,和現在大多數Java軟件一樣,Commons Chain可以使用XML文件作為配置文件。你可以將“汽車銷售”流程的步驟在XML文件中定義。這個文件有個規范的命名chain- config.xml。 清單9 <catalog> Chain的配置文件可以包含多個鏈定義,這些鏈定義可以集合進不同的編目中。在這個例子中,鏈定義在一個默認的編目中定義。事實上,你可以在這個文件中定義多個名字的編目,每個編目可擁有自己的鏈組。 現在你可以使用Commons Chain提供的類載入編目并得到指定的鏈,而不用像SellVehicleChain中那樣自己在程序中定義一組命令: 清單10 package com.jadecove.chain.sample; Chain 使用Commons Digester來讀取和解析配置文件。因此你需要將Commons Digester.jar加入classpath中。我使用了1.6版本并且工作得很好。Digester使用了Commons Collectios(我使用的版本是3.1),Commons Logging(版本1.0.4),Commons BeanUtils(1.7.0),因此你也需要將它們的jar文件加入classpath中。在加入這些jar后,CatalogLoader就可以被編譯和運行,它的輸出和另外兩個測試完全相同。 現在你可以在XML文件中定義鏈,并可以在程序中得到這個鏈(別忘了鏈也是命令),這樣擴展的可能性和程序的靈活性可以說是無限的。假設過程“安排財務”實際上由一個完全分離的商業部門處理。這個部門希望為這種銷售建立自己的工作流程。 Chain提供了嵌套鏈來實現這個要求。因為鏈本身就是命令,因此你可以用指向另一個鏈的引用替換一個單一用途的命令。下面是增加了新流程的鏈的定義: 清單11 <catalog name="auto-sales"> Commons Chain提供了一個常用的命令LookupCommand來查找和執行另一個鏈。屬性optional用于控制當指定的嵌套鏈沒有找到時如何處理。 optional=true時,即使鏈沒找到,處理也會繼續。反之,LookupCommand將拋出 IllegalArgumentException,告知指定的命令未找到。 在下面三種情況下,命令鏈將結束: 1.????????命令的execute方法返回true 2.????????運行到了鏈的盡頭 3.????????命令拋出異常 當鏈完全處理完一個過程后,命令就返回true。這是責任鏈模式(Chain of Responsibility)的基本概念。處理從一個命令傳遞到另一個命令,直到某個命令(Command)處理了這個命令。如果在到達命令序列盡頭時仍沒有處理返回true,也假設鏈已經正常結束。 當有命令拋出錯誤時鏈就會非正常結束。在Commons Chain中,如果有命令拋出錯誤,鏈的執行就會中斷。不論是運行時錯誤(runtime exception)還是應用錯誤(application exception),都會拋出給鏈的調用者。但是許多應用都需要對在命令之外定義的錯誤做明確的處理。Commons Chain提供了Filter接口來滿足這個要求。Filter繼承了Command,添加了一個名為postprocess的方法。 public boolean postprocess(Context context, Exception exception); 只要Filter的execute方法被調用,不論鏈的執行過程中是否拋出錯誤,Commons Chain都將保證Filter的postprocess方法被調用。和servlet的過濾器(filter)相同,Commons Chain的Filter按它們在鏈中的順序依次執行。同樣,Filter的postprocess方法按倒序執行。你可以使用這個特性實現自己的錯誤處理。下面是一個用于處理我們例子中的錯誤的Filter: 清單12 package com.jadecove.chain.sample; Filter在配置文件中的定義就和普通的命令(Command)定義相同: 清單13 <chain name="sell-vehicle"> Filter 的execute方法按定義的序列調用。然而,它的postprocess方法將在鏈執行完畢或拋出錯誤后執行。當一個錯誤被拋出時, postprocess方法處理完后會返回true,表示錯誤處理已經完成。鏈的執行并不會就此結束,但是本質上來說這個錯誤被捕捉而且不會再向外拋出。如果postprocess方法返回false,那錯誤會繼續向外拋出,然后鏈就會非正常結束。 讓我們假設ArrangeFinancing因為用戶信用卡損壞拋出錯誤。SellVehicleExceptionHandler就能捕捉到這個錯誤,程序輸出如下: Filter.execute() called. Get customer info Test drive the vehicle Negotiate sale Exception Bad credit occurred. 結合了過濾器(filter)和子鏈技術后,你就可以造型很復雜的工作流程。 Commons Chain是一個很有前途的框架,現在仍在開發,新的功能被頻繁地添加到其中。在下一篇關于Commons Chain的文章中,我們將研究Struts 1.3中是如何使用Commons Chain的。 Struts 1.3中用完全使用Commons Chain的類替換了原來的處理HTTP請求的類。如果你以前自己定制過Struts的請求處理(request processor),你將發現處理這個問題時Commons Chain為程序帶來了很好的靈活性。 posted @ 2006-08-22 10:40 Binary 閱讀(781) | 評論 (0) | 編輯 收藏 Log4J 最佳實踐之全能配置文件(轉)posted @ 2006-08-22 10:39 Binary 閱讀(265) | 評論 (0) | 編輯 收藏 Spring AOP中文教程
posted @ 2006-08-22 10:02 Binary 閱讀(315) | 評論 (0) | 編輯 收藏 Spring技巧之簡潔配置
Spring2.0在配置上調整了不少地方,增加更加靈活、簡潔的配置方式,本文通過兩個簡單的示例來演示。
??????配置Apache的一個數據源連接池,在Spring?2.0以前的版本中,我們可以使用類似下面的配置: <?xml?version="1.0"?encoding="UTF-8"?> ?<!DOCTYPE?beans?PUBLIC?"-//SPRING//DTD?BEAN//EN"?"http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <bean?id="dataSource" ??class="org.apache.commons.dbcp.BasicDataSource" ??destroy-method="close"> ??<property?name="driverClassName"> ???<value>org.gjt.mm.mysql.Driver</value></property> ??<property?name="url"> ???<value>jdbc:mysql://127.0.0.1:3306/easyjf-bbs</value>?</property> ??<property?name="username"><value>root</value>?</property> ??<property?name="password"><value>mypass</value></property> ?</bean> </beans> ??????在Spring2.0中,可以把<value>及<ref>兩個標簽作為其父級<bean>標簽的一個屬性來定義,這樣使得配置文件更加簡潔,如下所示: <?xml?version="1.0"?encoding="UTF-8"?> <!DOCTYPE?beans?PUBLIC?"-//SPRING//DTD?BEAN//EN"?"http://www.springframework.org/dtd/spring-beans.dtd"> <beans> <bean?id="dataSource" ??class="org.apache.commons.dbcp.BasicDataSource" ??destroy-method="close"> ??<property?name="driverClassName" ???value="org.gjt.mm.mysql.Driver"?/> ??<property?name="url" ???value="jdbc:mysql://127.0.0.1:3306/easyjf-bbs"?/> ??<property?name="username"?value="root"?/> ??<property?name="password"?value="mypass"?/> ?</bean> </beans> ?????? 另外,Spring2.0中還有一個非常實用的解析器,SimplePropertyNamespaceHandle,若配置文件中引用http: //www.springframework.org/schema/p命令空間,則將會使用 SimplePropertyNamespaceHandle來處理這個Bean的定義,可以在Spring2.0中的Bean中以更簡單的方式配置設值方法注入,如下所示: <?xml?version="1.0"?encoding="UTF-8"?> <beans?xmlns="http://www.springframework.org/schema/beans" ?xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" ?xmlns:p="http://www.springframework.org/schema/p" ?xsi:schemaLocation="http://www.springframework.org/schema/beans?http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean?id="dataSource" ??class="org.apache.commons.dbcp.BasicDataSource"?destroy-method="close" ??p:driverClassName="org.gjt.mm.mysql.Driver" ??p:url="jdbc:mysql://127.0.0.1:3306/easyjf-bbs"?p:username="root"?p:password="mysql"?/> </beans> 在上面的配置中,使用p:url則可以直接注入BasicDataSource的url屬性值,可以使用p:url-ref屬性來引用另外一個Bean。 ????如,Spring2.0以前的一個DAO配置: <bean?id="userDao"?class="com.easyjf.bbs.dbo.springjdbc.UserDaoSpringJdbc"> ???????<property?name="dataSource"><ref?bean="dataSource"/></property> ???</bean>?? ??使用簡短屬性方式,則改成如下: <bean?id="userDao"?class="com.easyjf.bbs.dbo.springjdbc.UserDaoSpringJdbc"?p:dataSource-ref="dataSource"?/> Spring2.0比以前的版本配置更加靈活、簡潔,如果手工書寫配置,則比較實用。當然,老的配置方式有很多開發工具如MyEclipse等都能識別,不需要我們動手一點一點的錄入配置文件,大多數工具對新的配置方式還不能識別,因此,請根據實際情況酌情使用。 posted @ 2006-08-22 09:56 Binary 閱讀(274) | 評論 (0) | 編輯 收藏 |
||||||||||||||||||