深入Hibernate的flush機制

一、理解flush機制


之後單純用原始的Hibernate框架做了一些驗證,並且打開執行SQL打印輸出臺的,得出的結論:


前提是在同一事務中間:



1、利用sql語句, session.createSQLQuery(sql).executeUpdate();進行插入,輸出臺打印出sql插入語句; 再利用sql語句,進行session.createSQLQuery(sql).uniqueResult(); 也會打印SQL查詢語句,沒有問題,可以查詢到數據。

 

2、利用hibernate封裝操作, session.save(entity); 進行插入,輸出臺並沒有打印出插入的SQL語句, 再利用 session.get(entity,id);方法做查詢 ;也沒有打印出SQL查詢語句,但是是可以查詢到數據的。到執行事務提交語句時,插入的SQL語句被打印出來

 

3、利用hibernate的session.save(entity); 進行插入,再利用《HQL》語句進行查詢,效果同上面第二點。

 

4、利用hibernate的session.save(entity); 進行插入,輸出臺並沒有打印出插入的SQL語句。 再利用sql語句,進行session.createSQLQuery(sql).uniqueResult(); 會打印SQL查詢語句。問題出現了,查詢不到任何數據。這種情況下利用session.flush()方法,在查詢之前執行到flush()方法,輸出臺會打印出插入的SQL語句。 再進行查詢就有數據。


驗證完成之後,查了下往上資料,對於第四點,在開發過程中出現頻繁,非常的常見,相信很多人都曾遇到,但又有很多人繼續摸不到頭腦。正好以此加深了印象。



從打印控制檯SQL可以看出一個基本的hibernate save方法的操作流程


1. 判斷所要保存的實例是否已處於持久化狀態,如果不是,則將其置入緩存;

 

2. 根據所要保存的實例計劃一條insert sql語句,注意只是計劃,並不執行;

 

3. 事務提交時執行之前所計劃的insert語句;



將tx.commit()換成session.flush,此時控制太打印出了insert語句,但是數據庫中並沒有添加新的記錄;



flush方法的主要作用就是清理緩存,強制數據庫與Hibernate緩存同步,以保證數據的一致性。它的主要動作就是向數據庫發送一系列的sql語句,並執行這些sql語句,但是不會向數據庫提交。而commit方法則會首先調用flush方法,然後提交事務。這就是爲什麼我們僅僅調用flush的時候記錄並未插入到數據庫中的原因,因爲只有提交了事務,對數據庫所做的更新纔會被保存下來。因爲commit方法隱式的調用了flush,所以一般我們都不會顯示的調用flush方法。

這是hibernate的flush機制。在一些複雜的對象更新和保存的過程中就要考慮數據庫操作順序的改變以及延時flush是否對程序的結果有影響。如果確實存在着影響,那就可以在需要保持這種操作順序的位置加入flush強制Hibernate將緩存中記錄的操作flush入數據庫,這樣看起來也許不太美觀,但很有效。


二、深入flush機制


先講解兩個常用方法:

session.evict(obj) :會把指定的緩衝對象進行清除。 
session.clear() :把緩衝區內的全部對象清除,但不包括操作中的對象。 


如果在save(obj)後,evict(obj),再事務提交會怎樣:



Hibernate 執行的順序如下: 



 (1) 生成一個事務的對象,並標記當前的 Session 處於事務狀態(注:此時並未啓動數據庫級事務)。 


 (2) 應用使用 s.save 保存對象,這個時候 Session 將這個對象放入 entityEntries ,用來標記對象已經和當前的會話建立了關聯,由於應用對對象做了保存的操作,Session 還要在 insertions 中登記應用的這個插入行爲(行爲包括:對象引用、對象 id 、 Session 、持久化處理類)。 


 (3)s.evict 將對象從 s 會話中拆離,這時 s 會從 entityEntries 中將這個對象移出。 


 (4) 事務提交,需要將所有緩存 flush 入數據庫, Session 啓動一個事務,並按照 insert(save),update,……,delete 的順序提交所有之前登記的操作(注意:所有 insert 執行完畢後纔會執行 update ,這裏的特殊處理也可能會將你的程序搞得一團糟,如需要控制操作的執行順序,要善於使用 flush ),現在對象不在 entityEntries中,但在執行 insert 的行爲時只需要訪問 insertions 就足夠了,所以此時不會有任何的異常。異常出現在插入後通知 Session 該對象已經插入完畢這個步驟上,這個步驟中需要將 entityEntries 中對象的 existsInDatabase 標誌置爲 true ,由於對象並不存在於 entityEntries 中,此時 Hibernate 就認爲 insertions 和 entityEntries可能因爲線程安全的問題產生了不同步(也不知道 Hibernate 的開發者是否考慮到例子中的處理方式,如果沒有的話,這也許算是一個 bug 吧),於是一個net.sf.hibernate.AssertionFailure 就被拋出,程序終止。 

     

    一般我們會錯誤的認爲 s.save 會立即執行,而將對象過早的與 Session 拆離,造成了 Session 的 insertions 和 entityEntries 中內容的不同步。所以我們在做此類操作時一定要清楚 Hibernate 什麼時候會將數據 flush 入數據庫,在未 flush 之前不要將已進行操作的對象從 Session 上拆離。解決辦法是在 save 之後,添加session.flush 。 


三、flush的設置


Flush方法是可以設置的,也就是 fulsh 什麼時候執行是可以設置的

 

 在session.beginTransaction 前設置 FlushMode

 

session.setFlushMode(FlushMode.Always|AUTO|COMMIT|NEVER|MANUAL)

 

FlushMode有 5 個值可選


Always:任何代碼都會 Flush 
AUTO:默認方式 – 自動 
Commit:COMMIT時 
Never:始終不 
MANUAL:手動方式


設置FlushMode  有個好處是可以節省開銷,比如默認 session 只做查詢時,就可以不讓他與數據庫同步了。



四、主鍵生成方式不同時,flush調用的時刻也不同


1、當主鍵的生成方式是uuid時:


    調用完save()後,只是將save的對象納入到了session的管理,不會發出insert語句,但是id已經生成,session中existsInDatebase狀態爲false(也就是說,此時數據庫中並不存在所save的對象);如果此時調用session.flush()方法,那麼Hibernate會清除緩存,執行相關的sql語句,則此時數據已經在數據庫中存在了,且如果數據庫的隔離級別設置成“未提交讀”時,我們應該可以在數據庫中讀到相關的數據記錄(此時的數據仍然可以“回滾”),顯然,session中existsInDatebase狀態將更改爲true;如果transaction.commit()方法被調用,在默認會調用session.flush()方法,同時,此時數據庫中的數據不能“回滾”。

2、當主鍵的生成方式爲native時:


調用完save()後,將save的對象納入到了session的管理,發出insert語句,並返回有數據庫生成的id,修改了session中existsInDatebase狀態爲true,如果數據庫的隔離級別設置爲爲提交讀,那麼我們可以看到save過的數據,這種情況下,顯示的調用session.flush()方法,已經顯的多餘了,因爲在後面的transaction.commit()方法被調用時,會隱式的調用session.flush()方法。

3、當主鍵的生成方式爲assigned時:


調用完save()後,將save的對象納入到了session的管理,不會發出insert語句,而此時的主鍵已經由我們手動分配了,於是,顯示的調用session.flush()方法,能起到主鍵生成方式爲uuid時的效果。

 



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章