序言
前一篇文章知道了什么是hibernate,並且創建了第一個hibernate工程,今天就來先談談hibernate的一級緩存和它的三種狀態,先要對着兩個有一個深刻的了解,才能對后面我要講解的一對多,一對一、多對多這種映射關系更好的理
--WZY
一、一級緩存和快照
什么是一級緩存呢?
很簡單,每次hibernate跟數據庫打交道時,都是通過session來對要操作的對象取得關聯,然后在進行操作,那么具體的過程是什么樣的呢?
1、首先session將一個對象加入自己的管理范圍內,其實也就是把該對象放入自己的一級緩存中,例如,session.save(xxx);這個語句就是將xxx保存在自己的一級緩存中,等待事務提交后,hibernate才真正的發sql語句,對數據庫進行操作。注意:session進行操作的時候,是將對象加入自己的一級緩存,並不是就直接跟數據庫打交道了。
2、在一級緩存中會做些什么事情呢?為什么能夠知道是發insert、還是update又或者delete呢?那這里就要提到一個快照的概念了,講講內部是什么原理。
舉例來說明問題吧。
session.save()
User user = new User(); user.setUsername("xiaoming"); user.setPassword("123"); session.save(user);//加入了一級緩存,這其中做了什么事情呢?看圖 user.setUsername("baibai");//那這里改了user的屬性,具體會發生什么事情呢 //提交事務 tx.commit(); //關閉session,持久化對象就會變為脫管狀態。 session.close();
發送的sql語句
//insert語句
Hibernate:
insert
into
user
(username, password, c_id)
values
(?, ?, ?)
//update語句
Hibernate:
update
user
set
username=?,
password=?,
c_id=?
where
id=?
session.get()
//通過圖中分析,通過select語句查詢,並且user會在session的一級緩存中, User user =(User)session.get(User.class, "8"); //會發出update語句。 user.setUsername("heihei");
分析圖
結果
//select語句 Hibernate: select user0_.id as id0_0_, user0_.username as username0_0_, user0_.password as password0_0_, user0_.c_id as c4_0_0_ from user user0_ where user0_.id=? //update語句 Hibernate: update user set username=?, password=?, c_id=? where id=?
在舉一個例子說明有一級緩存的存在,對於緩存來說,肯定有對緩存的一些操作,比如evict(xx)清除xx緩存、clear():清除一級緩存中所有數據、flush()刷出一級緩存(也就是不用事務提交緩存就刷出來了,就會發sql語句,事務提交也是一個刷出緩存的方法,) 就用上面這個get的例子
session.evict()
//通過圖中分析,通過select語句查詢,並且user會在session的一級緩存中, User user =(User)session.get(User.class, "15"); //將user移除session一級緩存 session.evict(user); //不會發出update語句。 user.setUsername("heihei");

結果就發送一條select查詢語句
Hibernate:
select
user0_.id as id0_0_,
user0_.username as username0_0_,
user0_.password as password0_0_,
user0_.c_id as c4_0_0_
from
user user0_
where
user0_.id=?
總結一下:通過上面的例子和講解,我們應該知道,並不是session一操作對象,就立馬會發sql語句,而是先將其保存到自己內部的一級緩存中,如果不手動刷出緩存,就會等待事務提交時刷出緩存,然后再進行發送sql語句,對數據庫進行操作。並且其中有一個快照區需要知道是干嘛的,理解了快照區,就可以知道每次會發送幾條sql語句。注意一點,session每次操作的是在一級緩存中的對象,不會操作快照區中的對象,快照區只是用來當作比較的一個地方,當對象加入一級緩存之后,外部在怎么改變,也只是改變快照區中的數據,並不是改變一級緩存中對象的屬性。這點就記清楚。如果還不是很清楚,等下面在帶你們看幾個例子,就瞬間明白了。
二、hibernate中對象的三種狀態
瞬時狀態transient、持久狀態(托管)persistent、游離(脫管)detached狀態
注意:托管、脫管要分清楚,分不清楚就用持久和游離
瞬時狀態:使用new操作符初始化的對象的狀態就是瞬時的,
1、在數據庫表中,沒有任何一條數據與它對應
2、不在session的緩存中
持久狀態:在session的緩存中,
1、在session的緩存中(注意,很多書上都覺得持久化狀態都在數據庫表中有相應記錄,這個是錯誤的,比如一個瞬時狀態的對象剛被session.save(),事務還沒提交,此時瞬時狀態就已經變為持久化狀態了,但是在數據庫中還沒有記錄)
2、在數據庫中可能有記錄。
脫管狀態:從session的緩存中移除出來了
1、是從session緩存中出來的,也就是從持久狀態轉變而來的,沒有別的方式能到達游離(脫管)狀態,只有這一種。
2、不在session的管理范圍內
3、在數據庫中有記錄
根據上面的文字性描述,可能還不太理解,那現在根據我畫的圖和官方給予的圖來加深印象吧。
誤區1、很多書上覺得通過id值就可以判斷對象是在哪個狀態,比如說,沒有id值,就是瞬時狀態,有id並且在session管理范圍內,就是持久狀態,有id不在session范圍內就是脫管狀態,
解釋:從上面的解釋來看,瞬時狀態變為脫管狀態,只要加上id就行了,但是從官方圖來說,瞬時狀態不能直接變為游離狀態,而游離狀態可以通過delete直接到達瞬時狀態(其實說直接,也是先將游離狀態的對象加入到session緩存中變為持久狀態,然后delete,在變為瞬時狀態而已。),那么上面所用的通過有沒有id值來判斷三種狀態就是有偏差的,可以這么理解,瞬時狀態在數據庫中就一定沒有對應的記錄,而游離狀態一定是通過持久狀態轉變而來的,並且在數據庫中可能有記錄(沒有記錄是因為多線程,有別的程序把那條記錄給刪除了,所以一般就覺得是有記錄的),所以瞬時狀態和游離狀態的區分點就在:從什么轉變而來的,如果直接new的,那么就是瞬時狀態對象,如果從持久態轉變的,那么就是游離狀態。
誤區2:很多書上或者博客中會說識別是不是持久化狀態,是看在session緩存內,還有就是在數據庫中有記錄。
解釋:這里的前半句對,但是后半句錯誤,持久狀態可能在數據庫中有記錄,也可能在數據庫中沒有記錄,說說沒有記錄的時候吧,就是當session.save保存瞬時狀態時,事務還沒有提交,對象還只是在session的一級緩存中,數據庫中就沒有該記錄,但此時它就已經是持久狀態對象了。
誤區3:認為session一操作,就會發出sql語句,這個上面已經說明白了,應該很清楚了。
小總結一下:怎么區分這三種狀態
1、如果是在session緩存中,那么就一定是持久狀態
2、如果是剛new出來的對象,那么就肯定是瞬時狀態
3、如果是從session緩存中出來的,也就是通過一些session.clear、ecivt等操作,清除了緩存的,那么就是游離狀態,
只有瞬時狀態能確定數據庫中沒有對應記錄,其他兩個狀態,都是不確定數據庫中是否有對應記錄
一切都以官方給出的圖為准,其他的快速識別狀態的方法,我認為是不可取的,自己還是沒弄明白其中的道理,也就永遠沒把握自己是不是對的。
講了那么久的理論,現在就通過那張官方給的圖,讓我們來實驗試驗各種狀態間的轉換吧。
1、怎么變成瞬時狀態、和如何從瞬時狀態變為持久狀態?
User user = new User();//瞬時狀態 user.setUsername("aaa");//瞬時狀態 session.save(user);//持久狀態,這里使用saceOrUpdate也一樣。將user放在session的一級緩存中,快照區也有一份 //提交事務 tx.commit();//事務提交之后,才發送sql語句 //關閉session session.close();//session關閉后,user就變為游離(脫管)狀態了
2、怎么直接變為持久狀態
//從數據庫中查詢id為7的用戶,並且此時user在session的一級緩存中,快照區也有一份一樣的 User user = (User) session.get(User.class, "7"); //提交事務 tx.commit(); //關閉session,持久化對象就會變為脫管狀態。 session.close();
3、持久狀態變為瞬時狀態
//並且user會在session的一級緩存中,持久狀態,快照區也有一份一樣的 User user =(User)session.get(User.class, "15"); //將user移除session一級緩存,並且從數據庫中刪除該記錄,快照區中的對象也刪除了,所以變成瞬時狀態而不是變成游離狀態 session.delete(user); //不會發出update語句。改變的只是瞬時狀態的屬性 user.setUsername("heihei");
4、持久狀態變為游離狀態
//並且user會在session的一級緩存中,持久狀態,快照區中也有一份一樣的 User user =(User)session.get(User.class, "15"); //將user移除session一級緩存,並沒有刪除數據庫記錄,所以變為了游離狀態,清除緩存,那么快照區中也沒了 session.evict(user); //不會發出update語句。改變的只是游離狀態的屬性,沒影響 user.setUsername("heihei");
5、游離狀態變為持久狀態
//並且user會在session的一級緩存中,持久狀態,快照區有一份一樣的 User user =(User)session.get(User.class, "15"); //將user移除session一級緩存,並沒有刪除數據庫記錄,所以變為了游離狀態。快照區中也沒了 session.evict(user); //不會發出update語句。改變的只是游離狀態的屬性,沒影響 user.setUsername("heihei"); //會發出update語句進行更新,重新回到持久狀態。加入到一級緩存中。快照區中也有一份一樣的。 session.update(user);
//猜一下這里會不會發兩條update語句?如果覺得是兩條的話,就沒記住我說的話,肯定是一條update語句呀,自己可以看上面講解快照區時的圖片,這里改變了快照區中user的屬性,
//因為上面用的是update方法,快照區會先跟一級緩存中的對比,如果有不一樣,那么就發送update語句,而不會先發一個update然后再對比,不一樣在發update,跟insert不一樣。
user.setUsername("sss");
6、游離狀態變為瞬時狀態
這個也沒什么好講的,上面已經分析過了,游離狀態直接變為瞬時狀態其實也就是先進過持久狀態,然后再變為瞬時狀態。
三、經過上面的學習,現在深入一點,讓你知道會發送多少條sql語句。
1、Test01
session = HibernateUtil.openSession(); session.beginTransaction(); User user = new User(); user.setUsername("aaa"); user.setPassword("aaa"); user.setBorn(new Date()); //以上u就是Transient(瞬時狀態),表示沒有被session管理並且數據庫中沒有 //執行save之后,被session所管理,而且,數據庫中已經存在,此時就是Persistent狀態 session.save(user); //此時u是持久化狀態,已經被session所管理,當在提交時,會把session中的對象和快照區的對象進行比較 //如果兩個對象中的值不一致就會繼續發出相應的sql語句 user.setPassword("bbb"); //此時會發出2條sql,一條用戶做插入,一條用來做更新 session.getTransaction().commit();
Hibernate: insert into t_user (born, password, username) values (?, ?, ?)
Hibernate: update t_user set born=?, password=?, username=? where id=?
Test02
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); session = HibernateUtil.openSession(); session.beginTransaction(); User u = new User(); u.setBorn(new Date()); u.setUsername("zhangsan"); u.setPassword("zhangsan"); session.save(u); u.setPassword("222"); //該條語句沒有意義 session.save(u); u.setPassword("zhangsan111"); //沒有意義 session.update(u); u.setBorn(sdf.parse("1988-12-22")); //沒有意義 session.update(u); session.getTransaction().commit();
沒有意義是什么意思呢?記得我一開始說的那個注意點嘛,所有的操作都會先存放在session的一級緩存中,當對象進入一級緩存中,在怎么改變屬性,都只改變快照區中對象的屬性,在用session進行操作,還是操作的一級緩存中的對象,記住了這一點,那么上面的程序就簡單了,u保存到了緩存中,u改變屬性,session在save u沒一點意義,並且連續改改,也只是改快照區中的屬性,等到事務提交的時候,在做對比和進行相應的操作。
Hibernate: insert into t_user (born, password, username) values (?, ?, ?)
Hibernate: update t_user set born=?, password=?, username=? where id=?
Test03
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); session = HibernateUtil.openSession(); session.beginTransaction(); User u = new User(); u.setId(5); //完成update之后也會變成持久化狀態 session.update(u); u.setBorn(sdf.parse("1998-12-22")); u.setPassword("lisi"); u.setUsername("lisi"); //會拋出異常 u.setId(333); session.getTransaction().commit();
這個例子會報異常,就是上面我說過的,在一級緩存中的對象,如果改變快照區中的id屬性,就會報異常
在這里,推薦一個博文,如果還想看更多的例子,就進去這個文章里面看,http://www.cnblogs.com/xiaoluo501395377/p/3380270.html