Hibernate向我們提供的主要的操縱數(shù)據(jù)庫(kù)的接口,Session就是其中的一個(gè),它提供了基本的增,刪,改,查方法.而且具有一個(gè)緩存機(jī)制,能夠按照某個(gè)時(shí)間點(diǎn),按照緩存中的持久化對(duì)象屬性的變化來(lái)更新數(shù)據(jù)庫(kù),著就是Session的緩存清理過(guò)程.在Hibernate中對(duì)象分為三個(gè)狀態(tài),臨時(shí),持久化,游離.如果我們希望JAVA里的一個(gè)對(duì)象一直存在,就必須有一個(gè)變量一直引用著這個(gè)對(duì)象.當(dāng)這個(gè)變量沒(méi)了.對(duì)象也就被JVM回收了.這篇博客我們就帶大家一起來(lái)看一下session的緩存機(jī)制,也就是hibernate的一級(jí)緩存,還有hibernate三種對(duì)象狀態(tài)的詳細(xì)情況。
當(dāng)Session的save()方法持久化一個(gè)Customer對(duì)象時(shí),Customer對(duì)象被加入到Session的緩存中,以后即使應(yīng)用程序中的引用變量不再引用Customer對(duì)象,只要Session的緩存還沒(méi)有被清空,Customer對(duì)象仍然處于生命周期中。 當(dāng)Session的load()方法試圖從數(shù)據(jù)庫(kù)中加載一個(gè)Customer對(duì)象時(shí),Session先判斷緩存中是否已經(jīng)存在這個(gè)Customer對(duì)象,如果存在,就不需要再到數(shù)據(jù)庫(kù)中檢索。 這樣就大大提高了hibernate查詢的時(shí)間效率,只有當(dāng)事務(wù)提交,session關(guān)閉之后,session緩存才會(huì)失效
下面我們來(lái)通過(guò)一段代碼來(lái)理解一下session緩存:
tx = session.beginTransaction();
Customer c1=newCustomer(“zhangsan",newHashSet());
//Customer對(duì)象被持久化,并且加入到Session的緩存中
session.save(c1);
Long id=c1.getId();
//c1變量不再引用Customer對(duì)象
c1=null;
//從Session緩存中讀取Customer對(duì)象,使c2變量引用Customer對(duì)象
Customer c2=(Customer)session.load(Customer.class,id);
tx.commit();
//關(guān)閉Session,清空緩存
session.close();
//訪問(wèn)Customer對(duì)象
System.out.println(c2.getName());
// c2變量不再引用Customer對(duì)象,此時(shí)Customer對(duì)象結(jié)束生命周期。
c2=null;
當(dāng)session調(diào)用save保存一個(gè)對(duì)象時(shí),這個(gè)對(duì)象就被加載到session緩存當(dāng)中,其實(shí)調(diào)用save方法這里有個(gè)細(xì)節(jié),很多人都忽略了這個(gè)細(xì)節(jié),就是save方法有一個(gè)返回值,返回一個(gè)seriaseble接口類型的數(shù)據(jù),我們知道像基本數(shù)據(jù)類型的包裝類型都實(shí)現(xiàn)了這個(gè)接口,其實(shí)這個(gè)返回值我們可以理解為保存對(duì)象的id,我們?cè)诤芏鄷r(shí)候都能用到這個(gè)返回值,這是一個(gè)應(yīng)該注意的地方。當(dāng)對(duì)象被save到緩存中時(shí),我們就可以調(diào)用對(duì)象的getid方法來(lái)獲得他的id了。在上面的示例中我們可以看到,雖然c1被復(fù)位null了,但是此時(shí)在session緩存里面還是有一個(gè)變量指向著該對(duì)象,所以該對(duì)象才不被垃圾回收器回收,當(dāng)我們?cè)诖死迷搶?duì)象的id去用load查詢時(shí),其實(shí)還是去到session緩存去找并且返回該對(duì)象,當(dāng)session關(guān)閉后。緩存清空。
下面我們?cè)趤?lái)看一個(gè)例子,來(lái)看一下get和load的另一個(gè)不同點(diǎn):
tx = session.beginTransaction();
Customer c1=(Customer)session.load(Customer.class,newLong(1));
Customer c2=(Customer)session.load(Customer.class,newLong(1));
System.out.println(c1==c2);// true or false ??
tx.commit();
session.close();
很明顯,這個(gè)示例最后打印出來(lái)的是true,因?yàn)樗麄儷@得的是同一個(gè)實(shí)例,我們具體來(lái)分析一下,我們?cè)谶\(yùn)行這段代碼時(shí),細(xì)心的童鞋應(yīng)該會(huì)發(fā)現(xiàn),利用load去查詢對(duì)象時(shí),沒(méi)有生成sql語(yǔ)句,這是為什么呢?既然查詢到結(jié)果了,為什么沒(méi)有生成出來(lái)sql語(yǔ)句呢。這就是我們要說(shuō)的load和get方法的第二個(gè)不同的地方了,load方法在查詢時(shí),其實(shí)是獲得的該對(duì)象的一個(gè)代理的對(duì)象,當(dāng)我們用到查詢到的對(duì)象時(shí),他才會(huì)去數(shù)據(jù)庫(kù)進(jìn)行查詢,如上,如果我們調(diào)用c1.getName方法,這時(shí)就會(huì)打印出sql語(yǔ)句來(lái),這時(shí)候他才真正的去數(shù)據(jù)庫(kù)查詢,而get方法,他在執(zhí)行g(shù)et的方法的時(shí)候就會(huì)去數(shù)據(jù)庫(kù)查詢,產(chǎn)生sql語(yǔ)句
Session緩存的作用
(1)減少訪問(wèn)數(shù)據(jù)庫(kù)的頻率。應(yīng)用程序從內(nèi)存中讀取持久化對(duì)象的速度顯然比到數(shù)據(jù)庫(kù)中查詢數(shù)據(jù)的速度快多了,因此Session的緩存可以提高數(shù)據(jù)訪問(wèn)的性能。
(2)保證緩存中的對(duì)象與數(shù)據(jù)庫(kù)中的相關(guān)記錄保持同步。當(dāng)緩存中持久化對(duì)象的狀態(tài)發(fā)生了變化,Session并不會(huì)立即執(zhí)行相關(guān)的SQL語(yǔ)句,這使得Session能夠把幾條相關(guān)的SQL語(yǔ)句合并為一條SQL語(yǔ)句,以便減少訪問(wèn)數(shù)據(jù)庫(kù)的次數(shù),從而提高應(yīng)用程序的性能。
Session的清理緩存
清理緩存是指按照緩存中對(duì)象的狀態(tài)的變化來(lái)同步更新數(shù)據(jù)庫(kù),下面我們還是具體來(lái)看一段代碼:以下程序代碼對(duì)Customer的name屬性修改了兩次:
tx = session.beginTransaction();
Customer customer=(Customer)session.load(Customer.class,
newLong(1));
customer.setName("Jack");
customer.setName("Mike");
tx.commit();
當(dāng)Session清理緩存時(shí),只需執(zhí)行一條update語(yǔ)句:
update CUSTOMERS set NAME= 'Mike'…… where ID=1;
其實(shí)第一次調(diào)用setName是無(wú)意義的,完全可以省略掉。
Session緩存在什么時(shí)候才清理呢?我們來(lái)看一下:
Session會(huì)在下面的時(shí)間點(diǎn)清理緩存:
1.當(dāng)應(yīng)用程序調(diào)用org.hibernate.Transaction的commit()方法的時(shí)候,commit()方法先清理緩存,然后再向數(shù)據(jù)庫(kù)提交事務(wù)。
2.當(dāng)應(yīng)用程序顯式調(diào)用Session的flush()方法的時(shí)候,其實(shí)這個(gè)方法我們幾乎很少用到,因?yàn)槲覀円话愣际窃谕瓿梢粋€(gè)事務(wù)才去清理緩存,提交數(shù)據(jù)更改,這樣我們直接提交事務(wù)就可以。
Hibernate中java對(duì)象的三種狀態(tài):
1、臨時(shí)狀態(tài)(transient):剛剛用new語(yǔ)句創(chuàng)建,還沒(méi)有被持久化,不處于Session的緩存中。處于臨時(shí)狀態(tài)的Java對(duì)象被稱為臨時(shí)對(duì)象。
2、持久化狀態(tài)(persistent):已經(jīng)被持久化,加入到Session的緩存中。處于持久化狀態(tài)的Java對(duì)象被稱為持久化對(duì)象。
3、游離狀態(tài)(detached):已經(jīng)被持久化,但不再處于Session的緩存中。處于游離狀態(tài)的Java對(duì)象被稱為游離對(duì)象。
持久化狀態(tài)和臨時(shí)狀態(tài)的不同點(diǎn)在于:
1、對(duì)象持久化狀態(tài)時(shí),他已經(jīng)和數(shù)據(jù)庫(kù)打交道了,在數(shù)據(jù)庫(kù)里面存在著該對(duì)象的一條記錄。
2、持久化狀態(tài)的對(duì)象存在于session的緩存當(dāng)中。
3、持久化狀態(tài)的對(duì)象有自己的OID。
游離狀態(tài)的對(duì)象與持久化狀態(tài)的對(duì)象不同體現(xiàn)在游離狀態(tài)的對(duì)象已經(jīng)不處于session的緩存當(dāng)中,并且在數(shù)據(jù)庫(kù)里面已經(jīng)不存在該對(duì)象的記錄,但是他依然有自己的OID。
對(duì)象的狀態(tài)轉(zhuǎn)換

我們一起來(lái)分析一下這個(gè)狀態(tài)轉(zhuǎn)換圖,首先一個(gè)對(duì)象被new出來(lái)之后,他是出于臨時(shí)狀態(tài)的,然后調(diào)用save或者saveOrUpdate方法把對(duì)象轉(zhuǎn)換為持久化狀態(tài),這里的saveOrUpdate方法其實(shí)是一個(gè)偷懶的方法,我們以前用的所有的save方法的地方都可以修改為該方法,這個(gè)方法是在保存數(shù)據(jù)之前先查看一下這個(gè)對(duì)象是什么狀態(tài),如果是臨時(shí)狀態(tài)就保存,如果是游離狀態(tài)就進(jìn)行更新。持久化狀態(tài)轉(zhuǎn)換成游離狀態(tài)可以是在session關(guān)閉或者被清理緩存時(shí),在或者就是調(diào)用evict方法,這個(gè)方法就是強(qiáng)行把對(duì)象從session緩存中清除。游離狀態(tài)裝換為持久化狀態(tài)可以調(diào)用update方法,其實(shí)update方法主要的功能就是把對(duì)象從游離狀態(tài)裝換成持久化狀態(tài)的,因?yàn)橐话愕母缕鋵?shí)不用這個(gè)方法也可以。
下面我們舉一個(gè)具體實(shí)例的看一下?tīng)顟B(tài)轉(zhuǎn)換過(guò)程:

這個(gè)圖需要大家仔細(xì)的理解一下,他很好的體現(xiàn)了對(duì)象生命周期的進(jìn)程和對(duì)象狀態(tài)的轉(zhuǎn)換。
下面我們?cè)谟靡粋€(gè)示例來(lái)看一下session的update方法是怎么把一個(gè)游離狀態(tài)的對(duì)象裝換成持久化的:
Customer customer=newCustomer();
customer.setName("Tom");
Session session1=sessionFactory.openSession();
Transaction tx1 = session1.beginTransaction();
session1.save(customer);
tx1.commit();
session1.close();//此時(shí)Customer對(duì)象變?yōu)橛坞x對(duì)象
Session session2=sessionFactory.openSession();
Transaction tx2 = session2.beginTransaction();
customer.setName(“zhangsan")//在和session2關(guān)聯(lián)之前修改Customer對(duì)象的屬性
session2.update(customer);
customer.setName(“l(fā)isi");//在和session2關(guān)聯(lián)之后修改Customer對(duì)象的屬性
tx2.commit();
session2.close();
當(dāng)session1保存完對(duì)象,然后事務(wù)關(guān)閉時(shí),對(duì)象就變?yōu)橛坞x狀態(tài)了,此時(shí)我們?cè)诖蜷_(kāi)一個(gè)session,利用update方法,在把對(duì)象和session關(guān)聯(lián)起來(lái),然后修改他的屬性,提交事務(wù)之后,游離狀態(tài)的對(duì)象一樣可以修改保存到數(shù)據(jù)庫(kù)中,這里雖然修改了兩次對(duì)象的屬性,但只會(huì)發(fā)送一條sql語(yǔ)句,因?yàn)閡pdate在修改對(duì)象數(shù)據(jù)時(shí),只有在事務(wù)提交時(shí),他才會(huì)發(fā)送sql語(yǔ)句進(jìn)行提交。所以只有最后一條修改信息管用。
總結(jié)一下Session的update()方法完成以下操作:
(1)把Customer對(duì)象重新加入到Session緩存中,使它變?yōu)槌志没瘜?duì)象。
(2)計(jì)劃執(zhí)行一個(gè)update語(yǔ)句。值得注意的是,Session只有在清理緩存的時(shí)候才會(huì)執(zhí)行update語(yǔ)句,并且在執(zhí)行時(shí)才會(huì)把Customer對(duì)象當(dāng)前的屬性值組裝到update語(yǔ)句中。因此,即使程序中多次修改了Customer對(duì)象的屬性,在清理緩存時(shí)只會(huì)執(zhí)行一次update語(yǔ)句。
Web應(yīng)用程序客戶層和業(yè)務(wù)邏輯層之間傳遞臨時(shí)對(duì)象和有利對(duì)象的過(guò)程:

Session的二級(jí)緩存
Hibernate提供了兩級(jí)緩存,第一級(jí)緩存是Session的緩存。由于Session對(duì)象的生命周期通常對(duì)應(yīng)一個(gè)數(shù)據(jù)庫(kù)事務(wù)或者一個(gè)應(yīng)用事務(wù),因此它的緩存是事務(wù)范圍的緩存。第一級(jí)緩存是必須的,不允許而且事實(shí)上也無(wú)法被卸除。在第一級(jí)緩存中,持久化類的每個(gè)實(shí)例都具有惟一的OID。 第二級(jí)緩存是一個(gè)可插拔的緩存插件,它由SessionFactory負(fù)責(zé)管理。由于SessionFactory對(duì)象的生命周期和應(yīng)用程序的整個(gè)進(jìn)程對(duì)應(yīng),因此第二級(jí)緩存是進(jìn)程范圍的緩存。這個(gè)緩存中存放的是對(duì)象的散裝數(shù)據(jù)。第二級(jí)緩存是可選的,可以在每個(gè)類或每個(gè)集合的粒度上配置第二級(jí)緩存。
Hibernate二級(jí)緩存結(jié)構(gòu)
