Hibernate樂觀并發控制
Hibernate樂觀并發控制(Optimistic concurrency control)唯一能夠同時保持高并發和高可伸縮性的方法就是使用帶版本化的Hibernate樂觀并發控制。版本檢查使用版本號、 或者時間戳來檢測更新沖突(并且防止更新丟失)。
Hibernate樂觀并發控制的代碼提供了三種可能的方法,應用程序在編寫這些代碼時,可以采用它們。我們已經在前面應用程序對話那部分展示了樂觀并發控制的應用場景,此外,在單個數據庫事務范圍內,版本檢查也提供了防止更新丟失的好處。
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 屬性使用
當然,如果你的應用是在一個低數據并發環境下,并不需要版本檢查的話,你照樣可以使用 這種方式,只不過跳過版本檢查就是了。在這種情況下,最晚提交生效 (last commit wins)就是你的長對話的默認處理策略。 請記住這種策略可能會讓應用軟件的用戶感到困惑,因為他們有可能會碰上更新丟失掉卻沒 有出錯信息,或者需要合并更改沖突的情況。
很明顯,手工進行版本檢查只適合于某些軟件規模非常小的應用場景,對于大多數軟件應用場景 來說并不現實。通常情況下,不僅是單個對象實例需要進行版本檢查,整個被修改過的關 聯對象圖也都需要進行版本檢查。作為標準設計范例,Hibernate使用擴展周期的 Session的方式,或者脫管對象實例的方式來提供自動版本檢查。
2. 擴展周期的session和自動版本化
單個 Session實例和它所關聯的所有持久化對象實例都被用于整個 對話,這被稱為session-per-conversation。Hibernate在同步的時候進行對象實例的版本檢查,如果檢測到并發修 改則拋出異常。由開發人員來決定是否需要捕獲和處理這個異常(通常的抉擇是給用戶 提供一個合并更改,或者在無臟數據情況下重新進行業務對話的機會)。
在等待用戶交互的時候, Session 斷開底層的JDBC連接。這種方式 以數據庫訪問的角度來說是***效的方式。應用程序不需要關心版本檢查或脫管對象實例 的重新關聯,在每個數據庫事務中,應用程序也不需要載入讀取對象實例。
- // foo is an instance loaded earlier by the old session
- Transaction t = session.beginTransaction(); // Obtain a new JDBC connection, start transaction
- foo.setProperty("bar");
- session.flush(); // Only for last transaction in conversation
- t.commit(); // Also return JDBC connection
- session.close(); // Only for last transaction in conversation
foo對象知道它是在哪個Session中被裝入的。在一個舊session中開啟一個新的數據庫事務,會導致session獲取一個新的連接,并恢復session的功能。將數據庫事務提交,使得session從JDBC連接斷開,并將此連接交還給連接池。在重新連接之后,要強制對你沒有更新的數據進行一次版本檢查,你可以對所有可能被其他事務修改過的對象,使用參數LockMode.READ來調用Session.lock()。你不用lock任何你正在更新的數據。
一般你會在擴展的Session上設置FlushMode.NEVER,因此只有***一個數據庫事務循環才會真正的吧整個對話中發生的修改發送到數據庫。因此,只有這***一次數據庫事務才會包含flush()操作,然后在整個對話結束后,還要close()這個session。
如果在用戶思考的過程中,Session因為太大了而不能保存,那么這種模式是有 問題的。舉例來說,一個HttpSession應該盡可能的小。由于 Session是一級緩存,并且保持了所有被載入過的對象,因此 我們只應該在那些少量的request/response情況下使用這種策略。你應該只把一個Session用于單個對話,因為它很快就會出現臟數據。
(注意,早期的Hibernate版本需要明確的對Session進行disconnec和reconnect。這些方法現在已經過時了,打開事務和關閉事務會起到同樣的效果。)
此外,也請注意,你應該讓與數據庫連接斷開的Session對持久層保持 關閉狀態。換句話說,在三層環境中,使用有狀態的EJB session bean來持有Session, 而不要把它傳遞到web層(甚至把它序列化到一個單獨的層),保存在HttpSession中。
擴展session模式,或者被稱為每次對話一個session(session-per-conversation), 在與自動管理當前session上下文聯用的時候會更困難。你需要提供你自己的CurrentSessionContext實現。請參閱Hibernate Wiki以獲得示例。
3. 脫管對象(deatched object)和自動版本化
這種方式下,與持久化存儲的每次交互都發生在一個新的Session中。 然而,同一持久化對象實例可以在多次與數據庫的交互中重用。應用程序操縱脫管對象實例 的狀態,這個脫管對象實例最初是在另一個Session 中載入的,然后 調用 Session.update(),Session.saveOrUpdate(), 或者 Session.merge() 來重新關聯該對象實例。
- // foo is an instance loaded by a previous Session
- foo.setProperty("bar");
- session = factory.openSession();
- Transaction t = session.beginTransaction();
- session.saveOrUpdate(foo); // Use merge() if "foo" might have been loaded already
- t.commit();
- session.close();
Hibernate會再一次在同步的時候檢查對象實例的版本,如果發生更新沖突,就拋出異常。
如果你確信對象沒有被修改過,你也可以調用lock() 來設置 LockMode.READ(繞過所有的緩存,執行版本檢查),從而取 代 update()操作。
4. 定制自動版本化行為
對于特定的屬性和集合,通過為它們設置映射屬性optimistic-lock的值 為false,來禁止Hibernate的版本自動增加。這樣的話,如果該屬性 臟數據,Hibernate將不再增加版本號。
遺留系統的數據庫Schema通常是靜態的,不可修改的?;蛘?,其他應用程序也可能訪問同一數據 庫,根本無法得知如何處理版本號,甚至時間戳。在以上的所有場景中,實現版本化不能依靠 數據庫表的某個特定列。在
有些情況下,只要更改不發生交錯,并發修改也是允許的。當你在
在以上所有場景中,不管是專門設置一個版本/時間戳列,還是進行全部字段/臟數據字段比較, Hibernate都會針對每個實體對象發送一條UPDATE(帶有相應的 WHERE語句 )的SQL語句來執行版本檢查和數據更新。如果你對關聯實體 設置級聯關系使用傳播性持久化(transitive persistence),那么Hibernate可能會執行不必 要的update語句。這通常不是個問題,但是數據庫里面對on update點火 的觸發器可能在脫管對象沒有任何更改的情況下被觸發。
因此,你可以在
以上就是對Hibernate樂觀并發控制的理解與分析。
【編輯推薦】