一、了解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時的效果。