1. 程式人生 > >Hibernate的session.flush做了什麼呢?

Hibernate的session.flush做了什麼呢?

  這是在一次事務提交時遇到的異常。
    an assertion failure occured (this may indicate a bug in Hibernate, but is more likely due to unsafe use of the session)
net.sf.hibernate.AssertionFailure: possible nonthreadsafe access to session
注:非possible non-threadsafe access to the session (那是另外的錯誤,類似但不一樣)

    這個異常應該很多的朋友都遇到過,原因可能各不相同。但所有的異常都應該是在flush或者事務提交的過程中發生的。這一般由我們在事務開始至事務提交的過程中進行了不正確的操作導致,也會在多執行緒同時操作一個Session時發生,這裡我們僅僅討論單執行緒的情況,多執行緒除了執行緒同步外基本與此相同。

    至於具體是什麼樣的錯誤操作那?我給大家看一個例子(假設Hibernate配置正確,為保持程式碼簡潔,不引入包及處理任何異常)


SessionFactory sf = new Configuration().configure().buildSessionFactory() ;
Session s = sf.openSession();
Cat cat = new Cat();

Transaction tran = s.beginTransaction(); (1)
s.save(cat); (2)(此處同樣可以為update delete)
s.evict(cat); (3)
tran.commit(); (4)
s.close();(5)


    這就是引起此異常的典型錯誤。我當時就遇到了這個異常,檢查程式碼時根本沒感覺到這段程式碼出了問題,想當然的認為在Session上開始一個事務,通過Session將物件存入資料庫,再將這個物件從Session上拆離,提交事務,這是一個很正常的流程。如果這裡正常的話,那問題一定在別處。

    問題恰恰就在這裡,我的想法也許沒有錯,但是一個錯誤的論據所引出的觀點永遠都不可能是正確的。因為我一直以為直接在對資料庫進行操作,忘記了在我與資料庫之間隔了一個Hibernate,Hibernate在為我們提供持久化服務的同時,也改變了我們對資料庫的操作方式,這種方式與我們直接的資料庫操作有著很多的不同,正因為我們對這種方式沒有一個大致的瞭解造成了我們的應用並未得到預先設想的結果。

那Hibernate的持久化機制到底有什麼不同那?簡單的說,Hibernate在資料庫層之上實現了一個快取區,當應用save或者update一個物件時,Hibernate並未將這個物件實際的寫入資料庫中,而僅僅是在快取中根據應用的行為做了登記,在真正需要將快取中的資料flush入資料庫時才執行先前登記的所有行為。

在實際執行的過程中,每個Session是通過幾個對映和集合來維護所有與該Session建立了關聯的物件以及應用對這些物件所進行的操作的,與我們這次討論有關的有entityEntries(與Session相關聯的物件的對映)、insertions(所有的插入操作集合)、deletions(刪除操作集合)、updates(更新操作集合)。下面我就開始解釋在最開始的例子中,Hibernate到底是怎樣運作的。
(1)生成一個事務的物件,並標記當前的Session處於事務狀態(注:此時並未啟動資料庫級事務)。
(2)應用使用s.save儲存cat物件,這個時候Session將cat這個物件放入entityEntries,用來標記cat已經和當前的會話建立了關聯,由於應用對cat做了儲存的操作,Session還要在insertions中登記應用的這個插入行為(行為包括:物件引用、物件id、Session、持久化處理類)。
(3)s.evict(cat)將cat物件從s會話中拆離,這時s會從entityEntries中將cat這個物件移出。
(4)事務提交,需要將所有快取flush入資料庫,Session啟動一個事務,並按照insert,update,……,delete的順序提交所有之前登記的操作(注意:所有insert執行完畢後才會執行update,這裡的特殊處理也可能會將你的程式搞得一團糟,如需要控制操作的執行順序,要善於使用flush),現在cat不在entityEntries中,但在執行insert的行為時只需要訪問insertions就足夠了,所以此時不會有任何的異常。異常出現在插入後通知Session該物件已經插入完畢這個步驟上,這個步驟中需要將entityEntries中cat的existsInDatabase標誌置為true,由於cat並不存在於entityEntries中,此時Hibernate就認為insertions和entityEntries可能因為執行緒安全的問題產生了不同步(也不知道Hibernate的開發者是否考慮到例子中的處理方式,如果沒有的話,這也許算是一個bug吧),於是一個net.sf.hibernate.AssertionFailure就被丟擲,程式終止。

我想現在大家應該明白例子中的程式到底哪裡有問題了吧,我們的錯誤的認為s.save會立即的執行,而將cat物件過早的與Session拆離,造成了Session的insertions和entityEntries中內容的不同步。所以我們在做此類操作時一定要清楚Hibernate什麼時候會將資料flush入資料庫,在未flush之前不要將已進行操作的物件從Session上拆離。

對於這個錯誤的解決方法是,我們可以在(2)和(3)之間插入一個s.flush()強制Session將快取中的資料flush入資料庫(此時Hibernate會提前啟動事務,將(2)中的save登記的insert語句登記在資料庫事務中,並將所有操作集合清空),這樣在(4)事務提交時insertions集合就已經是空的了,即使我們拆離了cat也不會有任何的異常了。
前面簡單的介紹了一下Hibernate的flush機制和對我們程式可能帶來的影響以及相應的解決方法,Hibernate的快取機制還會在其他的方面給我們的程式帶來一些意想不到的影響。看下面的例子:


(name為cat表的主鍵)
Cat cat = new Cat();
cat.setName(“tom”);
s.save(cat);

cat.setName(“mary”);
s.update(cat);(6)

Cat littleCat = new Cat();
littleCat.setName(“tom”);
s.save(littleCat);

s.flush();


這個例子看起來有什麼問題?估計不瞭解Hibernate快取機制的人多半會說沒有問題,但它也一樣不能按照我們的思路正常執行,在flush過程中會產生主鍵衝突,可能你想問:“在save(littleCat)之前不是已經更改cat.name並已經更新了麼?為什麼還會發生主鍵衝突那?”這裡的原因就是我在解釋第一個例子時所提到的快取flush順序的問題,Hibernate按照insert,update,……,delete的順序提交所有登記的操作,所以你的s.update(cat)雖然在程式中出現在s.save(littleCat)之前,但是在flush的過程中,所有的save都將在update之前執行,這就造成了主鍵衝突的發生。

這個例子中的更改方法一樣是在(6)之後加入s.flush()強制Session在儲存littleCat之前更新cat的name。這樣在第二次flush時就只會執行s.save(littleCat)這次登記的動作,這樣就不會出現主鍵衝突的狀況。

再看一個例子(很奇怪的例子,但是能夠說明問題)

Cat cat = new Cat();
cat.setName(“tom”);
s.save(cat); (7)
s.delete(cat);(8)

cat.id=null;(9)
s.save(cat);(10)
s.flush();


這個例子在執行時會產生異常net.sf.hibernate.HibernateException: identifier of an instance of Cat altered from 8b818e920a86f038010a86f03a9d0001 to null

這裡例子也是有關於快取的問題,但是原因稍有不同:
(7)和(2)的處理相同。
(8)Session會在deletions中登記這個刪除動作,同時更新entityEntries中該物件的登記狀態為DELETED。
(9)Cat類的識別符號欄位為id,將其置為null便於重新分配id並儲存進資料庫。
(10)此時Session會首先在entityEntries查詢cat物件是否曾經與Session做過關聯,因為cat只改變了屬性值,引用並未改變,所以會取得狀態為DELETED的那個登記物件。由於第二次儲存的物件已經在當前Session中刪除,save會強制Session將快取flush才會繼續,flush的過程中首先要執行最開始的save動作,在這個save中檢查了cat這個物件的id是否與原來執行動作時的id相同。不幸的是,此時cat的id被賦為null,異常被丟擲,程式終止(此處要注意,我們在以後的開發過程儘量不要在flush之前改變已經進行了操作的物件的id)。

這個例子中的錯誤也是由於快取的延時更新造成的(當然,與不正規的使用Hibernate也有關係),處理方法有兩種:
1、在(8)之後flush,這樣就可以保證(10)處save將cat作為一個全新的物件進行儲存。
2、刪除(9),這樣第二次save所引起的強制flush可以正常的執行,在資料庫中插入cat物件後將其刪除,然後繼續第二次save重新插入cat物件,此時cat的id仍與從前一致。

這兩種方法可以根據不同的需要來使用,呵呵,總覺得好像是很不正規的方式來解決問題,但是問題本身也不夠正規,只希望能夠在應用開發中給大家一些幫助,不對的地方也希望各位給與指正。

  總的來說,由於Hibernate的flush處理機制,我們在一些複雜的物件更新和儲存的過程中就要考慮資料庫操作順序的改變以及延時flush是否對程式的結果有影響。如果確實存在著影響,那就可以在需要保持這種操作順序的位置加入flush強制Hibernate將快取中記錄的操作flush入資料庫,這樣看起來也許不太美觀,但很有效。 

相關推薦

Hibernate的session.flush什麼

  這是在一次事務提交時遇到的異常。    an assertion failure occured (this may indicate a bug in Hibernate, but is more likely due to unsafe use of the sess

為什麼有些事情我們感覺會後悔,但是我們還是會去

1.首先例舉那些我們做了會後悔的事情,比如浪費時間,整天玩遊戲,整天刷劇 2.其次是選擇   如果我們不玩遊戲,那我們幹什麼呢?這就是另一個值得思考的問題,現代人的生活好像並不能閒下來,並不能仔細品味安靜的生活,一旦閒下來人們就會感到恐慌 這也是人們尋找通過那些不重要的事情來表達自己的一個原

快6年研發的女程式設計師,轉行什麼合適

      2012年10月份我來到北京,開始從事軟體開發(JAVA程式設計師)工作,到現在已經有5年多的時間了, 技術能力一般,剛開始的一年半時間在一家做交通行業的公司打雜,由於當時是公司裡年齡最小的,而且剛實習技術不

為什麼過35歲就不適合技術

在中國有種普遍的認識:做技術的過了 35歲,就需要考慮自己的出路問題了。或者轉向做技術主管等管理方向,或者轉為市場銷售方向。因為主管畢竟是有限的,所以很多技術過了35歲以後就開始了轉行。但是我們在看國外很多資料的時候發現,很多大師和大牛,過了40歲甚至五十歲還在一線工作,甚至仍然有很多創造性的貢獻

【IT職業】從來沒有人告訴你離職前要的事 (收藏下,萬一哪天用到

據哈佛商學院教授、《行動起來:擁抱不確定性並創造未來》一書的合著者倫納德·施萊辛格所說:“如同書擋一樣,你如何開始與如何結束,是任何職業關係中最為重要的部分。”但問題是,人們總是會花很多時間準備與策劃如何給別人留下深刻的第一印象,卻很少考慮到“最後印象”。無論你是為何理由

朋友國企幹5年java,居然不知道Dubbo是什麼?我真信

點贊再看,養成習慣,微信搜一搜【三太子敖丙】關注這個喜歡寫情懷的程式設計師。 本文 GitHub https://github.com/JavaFamily 已收錄,有一線大廠面試完整考點、資料以及我的系列文章。 前言 接下來一段時間敖丙將帶大家開啟緊張刺激的 Dubbo 之旅!是的要開始寫 Dubbo

有些事現在不,一輩子都不會

做了 some 放下 only sta iou sim 自己的 don 【有些事現在不做,一輩子都不會做了】   年輕的時候,總是想著,等到怎樣怎樣,我就怎樣怎樣。   等到忙完這一陣,我就健身減肥;   等到忙完這一陣,我就拿起kindle充電;   等到忙完這一

創建一個對象都在內存中什麽事情

什麽 分配 首地址 位置 內存 一個 new class文件 class 創建一個對象都在內存中做了什麽事情? 1:先將硬盤上指定位置的Person.class文件加載進內存。 2:執行main方法時,在棧內存中開辟了main方法的空間(壓棧-進棧),然後在main方法的棧

在對Activity基類的封裝中,我什麽

例如 stream color tin ase throw this reset ack 在開發實踐中,不同Activity有很多代碼是反復冗余的。因此非常有必要將這部分抽取出來。封裝一個繼承自Activity的類,命名為BaseActivity。 翻看之前寫過的代碼

Delphi XE5 Android Dialogs 對話框(模擬一套)

系統 cto str and 圖片 download iss top mar 最近要在Android中使用對話框, 但發現無現成的, TOpenDialog等已經不支持移動設備,還好系統提供了一些文件目錄函數可用,於是簡單的模擬了一個,支持OpenDialog ,SaveD

今天學習js些總結,分享給大家

要求 js文件 優先 encode mas src 全局變量 nan span 一、1.javascript的作用   是基於對象和事件驅動的語言,應用於客戶端 基於對象:提供好了很多對象,可以直接拿過來使用,不需要創建 事件驅動: html做網站靜態效果,jav

一個網站 測試一下營銷效果外匯VPS

詳細 美國 blank 最快 機房 vps bsp 營銷 法國 外匯VPS 專註於提供掛EA的VPS 機房位於美國芝加哥與法國歐洲機房。 提供給EA交易者最快的響應速度。 MT4VPS 外匯VPS EAVPS 回頭詳細描述一下效果做了一個網站 測試一下營銷效果

LeetCode 93. Restore IP Addresses 20170705 部分之前沒寫的題目

git 如果 str sse 1.3 store 一個 clas 根據 Given a string containing only digits, restore it by returning all possible valid IP address combin

LeetCode 113. Path Sum II 20170705 部分之前沒寫的題目

for list 路徑 父節點 leetcode pathsum and left -1 Given a binary tree and a sum, find all root-to-leaf paths where each path‘s sum equals the

4、看一些代碼,更新的規劃,從全局著手

python讓編程變得好玩——至少對我來說是這樣。只有感到編程是有趣的時候,才容易富有成效。好玩的編程(Playful Programming)是我發明的詞,是極限編程(Extreme Programming,亦稱XP)的非極限版本。XP的大多數點子我都喜歡,但是我太懶了,不能完全遵守它的原則。所以現在挑了一

【dotnet跨平臺】"dotnet restore"和"dotnet run"都些什麽?

sin end ati article align ade exce font aspnet 【dotnet跨平臺】"dotnet restore"和"dotnet run"都做了些什麽?前言:關於dotnet跨平臺的相關內容。能

【心得】只有當你去,你才能真正體會它

天下 成了 什麽 同學 一個 nbsp 做了 bsp 兩個   來深圳工作已經快兩個月了,我同學住我樓下,我來的那天,她就幾乎每天下班回來都會玩“王者榮耀”,她說很好玩,很多人都在玩。 對此,我不以為然,覺得不就是打打殺殺的嗎,有什麽好玩的。   前幾天,她來我樓上蹭網

UglifyJS-- 對你的js什麽

else 處理 fail class clas 逗號語句 執行函數 驗證 後來 也不是閑著沒事去看壓縮代碼,但今天調試自己代碼的時候發現有點意思。因為是自己寫的,雖然壓縮了,格式化之後還是很好辨認。當然作為min的首要準則不是可讀性,而是精簡。那麽它會盡量的縮短代碼,盡量的

涉嫌壟斷的App Store,到底什麽讓開發者暴怒

app store什麽行業最賺錢?不是你想象中的餐飲、互聯網、鉆石,而是壟!斷!行!業!不管是壟斷什麽,只要一家獨大,就能任意制定價格和遊戲規則,將利益最大化。還有掌控整個鏈條上參與者的“生殺大權”,不亦快哉!但一旦玩過火,也會激起激烈的反抗。而近日,蘋果App Store就成為了眾矢之的。限於蘋果App S

很多年的擇校服務,感慨萬千

http 小學 .sh log 服務 blog shtml pos 估計 說真的,做了好幾年的擇校服務,感覺現在家長和孩子的壓力一年比一年大。考一些私立小學的孩子,遇到的題目是日新月異,家長要面對的擇校服務費也是一天比一天貴,明年龍寶寶幼升小,豬寶寶小升初,估計很多學校的名