hibernat之一級緩存的原理

Po對象

Po對象的三種狀態以及相互轉化的結果

Po對象處於瞬時(臨時)狀態和脫管狀態的時候,都不能直接於數據庫交互

只有對象處於持久化狀態的時候,才能被保存到數據庫

通俗的解釋三種狀態:

瞬時態:通過new出來的對象,如果是自然主鍵,則必須指定主鍵。如果是代理主鍵(主鍵己增長)可以沒有主鍵(即使有後來也會被掩蓋)

持久態:必須在session處於連接狀態,且必須有主鍵(主鍵自增長除外),可以與數據庫不一樣(該對象的主鍵可以再數據庫找不到)

託管態:可以通過new出來,但必須有主鍵卻必須在數據庫能找到。

三種狀態的相互轉化:

瞬時態(產生)轉化爲持久態:只需要通過save即可,通過sessionsave方法就把一個瞬時態的po對象就變成了持久化狀態,

注意:如果是代理主鍵,即使在瞬時態的時候指定主鍵(new的時候給了主鍵),save以後也都會被掩蓋,不管數據庫有沒有與之該對應的,因爲如果是代理主鍵,save會發生搶佔主鍵的動作,不管最後有沒有commit,都會去數據庫搶主鍵。如果是自然主鍵則不會。

持久態的產生:必須在session處於連接的情況下的(且擁有oId),且經過save的臨時po對象才能變成持久態,還有就是在session連接狀態下,通過get方法獲取的一個對象,這中對象不是有瞬時態轉化來的,是直接通過id(主鍵)到數據庫查詢出來的,所有該po對象直接是持久態。

脫管態的轉化:當session關閉以後所有的持久態po對象都變成了拖管狀態,所以託管態是一個有id(主鍵)op對象,除了關閉session能把持久態的op轉化爲脫管態以外,給一個瞬時態的id賦值(必須在數據庫有對應)也能把一個瞬時態轉化爲託管態。

託管態可以再次轉化爲持久態,update能把一個託管態轉化爲持久態,其實還有一個方法也可以save,不過這個方法變數太多,要通過save方法吧一個託管態轉化爲持久態,必須在save之前沒有get(通過該託管對象的id(主鍵)到數據庫去查詢)。說的超前一點,必須一級緩存裏面沒有改對象(沒有id與該對象相同),才能save進去,不然會報錯。而且通過這種方式save進去的對象,如果沒有經過refresh而直接提交,會把數據庫裏面的原來的數據掩蓋。

一級緩存

生命週期:一級緩存的本質是一個map集合,當session創建的時候,map緩存也就產生,當session關閉是,map緩存也就銷燬和釋放。

緩存的數據:當session打開的時候必然要與數據庫連接,那麼連接對象什麼時候產生,什麼時候銷燬,怎麼樣保存呢?其實當session打開的時候,就會與數據庫產生一個連接,並把這個連接一直保存(緩存)在一級緩存(map)裏面,通過多次查詢同一個對象(在session處於連接狀態下),可以發現,其實是得到的是同一個對象(內存地址相同),說明在第一次查詢的時候產生一個對象,第二次查詢的時候沒有產生對象,而是直接獲取到了上一個的對象,說明第一次查詢的時候,產生了對象並把該對象緩存起來了(保存到緩存),當以後查詢的時候,就向數據庫發請求,這樣就大大的增加了查詢效率,比較快。

一級緩存原理:當數據被緩存起來了以後,那修改的時候腫麼辦呢?被緩存的po對象(主要講解po對象,其實還緩存了很多對象)肯定都是持久化的狀態。當請求吧吧數據從數據加載到內存以後,jvm爲每個po對象分配一個內存地址,hibernate內部機制在當數據加載到內存的同事,還吧該數據備份了一份(專業叫快照),這纔是一級緩存的核心,備份的對象被保存到一級緩存(map)通過鍵值對(key-value)的方式保存了該副本,並且該副本不能主動(手動設置該對象的屬性值)更改,保存的key就是該對象(本體)在內存中的地址(這個地址有jvm自動分配的),value對呀該對象的副本,所以當數據(其實是數據副本)被保存到一級緩存以後,此時內存中的局面就成了,po對象本體在內存中,一級緩存中保存了該po對象的副本和本體的內存地址,如果再次查詢,首先到內存中區查找看有沒有符合條件的,如果有則把請求返回的對象的內存地址指向在內存中已經存在且滿足條件的地址地址,那麼這次請求得到的對象就是上次的對象。,如果沒有則想數據庫發請求,然後把返回的對象加載到內存,併爲返回的對象分配內存地址,此時hibernate按照上次的方法吧該對象再次緩存起來(備份到一級緩存),依次類推。快照(備份到一級緩存)中的數據不能主動的被更改。只能通過持久狀態的po對象去修改,當持久化對象的po對象的屬性值發生變化的時候,調用set屬性,或者update該對象的時候,會去比較該po對象的本體與副本的差別,如果發現不一樣,則修改快照(一級緩存中副本)的屬性值,並始終讓副本一本體的屬性值保持一致,其實調用po對象的set屬性的時候隱式的觸發了update方法,可以發現當你在set第一個屬性值的時候,在通過id(主鍵)get該對象的是時候發現第一個屬性值已經改變,在set第二個屬性,再通過id(主鍵)get該對象的是時候發現第二個屬性值已經改變,一次類推,每set一次屬性,get一次對象,發現對象屬性的更改書瞬間完成的,不是等你set完了再一次性更改副本,這樣使的更改更加及時和精確。所以用戶(程序員)只能手動更改ppo對象的屬性值,而不能直接更改快照裏面的值,只有hibernate自己才能更改快照。其實更改原理就是當po對象的屬性值發生變化的時候,通過該po對象的內存地址去一級緩存或者說快照裏通過key(此時key就是該po對象的內存地址)起找到該對象的副本然後對比修改。隨帶提一下,不管是update,還是set,只要最後沒有commit,數據都不會保存到數據庫,所有的修改和變化都是內存和快照之間的數據交互和統一。

(一級)緩存的刷出和統一:這裏說的統一主要是主要是內存的數據與快照的數據統一,與數據庫無關。而刷出則是內存的數據和數據庫的數據保持一致。緩存的統一通過flush(其實如果不出意外,內存的數據和快照的數據應該是保持一致的),因爲快照的數據不能手動修改也沒有辦法證實,其實每次在commit提交數據的時候都會隱式的調用flush方法,但是與數據庫無關可以證明,首先在flush之前加一個斷點,然後讓程序執行2get方法並打印,第一次打印和第二次get之間加一個flush,當控制檯打印第一次get對象的數據和內存地址的時候(此時還沒有執行第二次get方法),然後手動的去數據庫修改數據庫的字段值(修改並提交),然後程序繼續向下,get第二次,並打印,你會發現,裏面的內容(對象屬性值)和內存地址一模一樣,說明flush與數據無關。緩存的刷出通過refersh方法,此時是以數據庫數據爲標準,然後更新內存中的數據(po對象的屬性值,內存地址沒有改),同樣的方法,get兩次打印2次,在第一次打印和第二次get之間refersh,並加上斷點(和上面的方法一模一樣,只是吧flush換成refersh),你會發現當你吧數據庫字段的值修改提交以後,2次打印的數據不一樣(對象屬性值),但是值得注意的是內存地址居然是一模一樣的,說明刷出模式,值修改了對象的內容,並沒有吧對象銷燬在重造。

 

 

 

 

 

 

 

 

 

 

 

 

 

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