hibernate提供的一級(jí)緩存
hibernate是一個(gè)線(xiàn)程對(duì)應(yīng)一個(gè)session,一個(gè)線(xiàn)程可以看成一個(gè)用戶(hù)。也就是說(shuō)session級(jí)緩存(一級(jí)緩存)只能給一個(gè)線(xiàn)程用,別的線(xiàn)程用不了,一級(jí)緩存就是和線(xiàn)程綁定了。
hibernate一級(jí)緩存生命周期很短,和session生命周期一樣,一級(jí)緩存也稱(chēng)session級(jí)的緩存或事務(wù)級(jí)緩存。如果tb事務(wù)提交或回滾了,我們稱(chēng)session就關(guān)閉了,生命周期結(jié)束了。
緩存和連接池的區(qū)別:緩存和池都是放在內(nèi)存里,實(shí)現(xiàn)是一樣的,都是為了提高性能的。但有細(xì)微的差別,池是重量級(jí)的,里面的數(shù)據(jù)是一樣的,比如一個(gè)池里放100個(gè)Connection連接對(duì)象,這個(gè)100個(gè)都是一樣的。緩存里的數(shù)據(jù),每個(gè)都不一樣。比如讀取100條數(shù)據(jù)庫(kù)記錄放到緩存里,這100條記錄都不一樣。
緩存主要是用于查詢(xún)
//同一個(gè)session中,發(fā)出兩次load方法查詢(xún)
Student student = (Student)session.load(Student.class, 1);
System.out.println("student.name=" + student.getName());
//不會(huì)發(fā)出查詢(xún)語(yǔ)句,load使用緩存
student = (Student)session.load(Student.class, 1);
System.out.println("student.name=" + student.getName()); |
第二次查詢(xún)第一次相同的數(shù)據(jù),第二次load方法就是從緩存里取數(shù)據(jù),不會(huì)發(fā)出sql語(yǔ)句到數(shù)據(jù)庫(kù)里查詢(xún)。
//同一個(gè)session,發(fā)出兩次get方法查詢(xún)
Student student = (Student)session.get(Student.class, 1);
System.out.println("student.name=" + student.getName());
//不會(huì)發(fā)出查詢(xún)語(yǔ)句,get使用緩存
student = (Student)session.get(Student.class, 1);
System.out.println("student.name=" + student.getName()); |
第二次查詢(xún)第一次相同的數(shù)據(jù),第二次不會(huì)發(fā)出sql語(yǔ)句查詢(xún)數(shù)據(jù)庫(kù),而是到緩存里取數(shù)據(jù)。
//同一個(gè)session,發(fā)出兩次iterate查詢(xún)實(shí)體對(duì)象
Iterator iter = session.createQuery
("from Student s where s.id<5").iterate();
while (iter.hasNext()) {
Student student = (Student)iter.next();
System.out.println(student.getName());
}
System.out.println("--------------------------------------");
//它會(huì)發(fā)出查詢(xún)id的語(yǔ)句,但不會(huì)發(fā)出根據(jù)id查詢(xún)學(xué)生的語(yǔ)句,因?yàn)閕terate使用緩存
iter = session.createQuery("from Student s where s.id<5").iterate();
while (iter.hasNext()) {
Student student = (Student)iter.next();
System.out.println(student.getName());
} |
一說(shuō)到iterater查詢(xún)就要立刻想起:iterater查詢(xún)?cè)跊](méi)有緩存的情況下會(huì)有N+1的問(wèn)題。
執(zhí)行上面代碼查看控制臺(tái)的sql語(yǔ)句,第一次iterate查詢(xún)會(huì)發(fā)出N+1條sql語(yǔ)句,第一條sql語(yǔ)句查詢(xún)所有的id,然后根據(jù)id查詢(xún)實(shí)體對(duì)象,有N個(gè)id就發(fā)出N條語(yǔ)句查詢(xún)實(shí)體。
第二次iterate查詢(xún),卻只發(fā)一條sql語(yǔ)句,查詢(xún)所有的id,然后根據(jù)id到緩存里取實(shí)體對(duì)象,不再發(fā)sql語(yǔ)句到數(shù)據(jù)庫(kù)里查詢(xún)了。
//同一個(gè)session,發(fā)出兩次iterate查詢(xún),查詢(xún)普通屬性
Iterator iter = session.createQuery(
"select s.name from Student s where s.id<5").iterate();
while (iter.hasNext()) {
String name = (String)iter.next();
System.out.println(name);
}
System.out.println("--------------------------------------");
//iterate查詢(xún)普通屬性,一級(jí)緩存不會(huì)緩存,所以發(fā)出查詢(xún)語(yǔ)句
//一級(jí)緩存是緩存實(shí)體對(duì)象的
iter = session.createQuery
("select s.name from Student s where s.id<5").iterate();
while (iter.hasNext()) {
String name = (String)iter.next();
System.out.println(name);
} |
執(zhí)行代碼看控制臺(tái)sql語(yǔ)句,第一次發(fā)出N+1條sql語(yǔ)句,第二次還是發(fā)出了N+1條sql語(yǔ)句。因?yàn)?/span>一級(jí)緩存只緩存實(shí)體對(duì)象,tb不會(huì)緩存普通屬性,所以第二次還是發(fā)出sql查詢(xún)語(yǔ)句。
//兩個(gè)session,每個(gè)session發(fā)出一個(gè)load方法查詢(xún)實(shí)體對(duì)象
try {
session = HibernateUtils.getSession();
session.beginTransaction();
Student student = (Student)session.load(Student.class, 1);
System.out.println("student.name=" + student.getName());
session.getTransaction().commit();
}catch(Exception e) {
e.printStackTrace();
session.getTransaction().rollback();
}finally {
HibernateUtils.closeSession(session);
}
第二個(gè)session調(diào)用load方法
try {
session = HibernateUtils.getSession();
session.beginTransaction();
Student student = (Student)session.load(Student.class, 1);
//會(huì)發(fā)出查詢(xún)語(yǔ)句,session間不能共享一級(jí)緩存數(shù)據(jù)
//因?yàn)樗麜?huì)伴隨著session的消亡而消亡
System.out.println("student.name=" + student.getName());
session.getTransaction().commit();
}catch(Exception e) {
e.printStackTrace();
session.getTransaction().rollback();
}finally {
HibernateUtils.closeSession(session);
} |
第一個(gè)session的load方法會(huì)發(fā)出sql語(yǔ)句查詢(xún)實(shí)體對(duì)象,第二個(gè)session的load方法也會(huì)發(fā)出sql語(yǔ)句查詢(xún)實(shí)體對(duì)象。因?yàn)閟ession間不能共享一級(jí)緩存的數(shù)據(jù),所以第二個(gè)session的load方法查詢(xún)相同的數(shù)據(jù)還是要到數(shù)據(jù)庫(kù)中查詢(xún),因?yàn)樗也坏降谝粋€(gè)session里緩存的數(shù)據(jù)。
//同一個(gè)session,先調(diào)用save方法再調(diào)用load方法查詢(xún)剛剛save的數(shù)據(jù)
Student student = new Student();
student.setName("張三");
//save方法返回實(shí)體對(duì)象的id
Serializable id = session.save(student);
student = (Student)session.load(Student.class, id);
//不會(huì)發(fā)出查詢(xún)語(yǔ)句,因?yàn)?/span>save支持緩存
System.out.println("student.name=" + student.getName()); |
先save保存實(shí)體對(duì)象,再用load方法查詢(xún)剛剛save的實(shí)體對(duì)象,則load方法不會(huì)發(fā)出sql語(yǔ)句到數(shù)據(jù)庫(kù)查詢(xún)的,而是到緩存里取數(shù)據(jù),因?yàn)?/span>save方法也支持緩存。當(dāng)然前提是同一個(gè)session。
//大批量的數(shù)據(jù)添加
for (int i=0; i<100; i++) {
Student student = new Student();
student.setName("張三" + i);
session.save(student);
//每20條更新一次
if (i % 20 == 0) {
session.flush();
//清除緩存的內(nèi)容
session.clear();
}
} |
大批量數(shù)據(jù)添加時(shí),會(huì)造成內(nèi)存溢出的,因?yàn)閟ave方法支持緩存,每save一個(gè)對(duì)象就往緩存里放,如果對(duì)象足夠多內(nèi)存肯定要溢出。一般的做法是先判斷一下save了多少個(gè)對(duì)象,如果save了20個(gè)對(duì)象就對(duì)緩存手動(dòng)的清理緩存,這樣就不會(huì)造成內(nèi)存溢出。
注意:清理緩存前,要手動(dòng)調(diào)用flush方法同步到數(shù)據(jù)庫(kù),否則save的對(duì)象就沒(méi)有保存到數(shù)據(jù)庫(kù)里。
注意:大批量數(shù)據(jù)的添加還是不要使用hibernate,這是hibernate弱項(xiàng)。可以使用jdbc(速度也不會(huì)太快,只是比hibernate好一點(diǎn)),或者使用工具產(chǎn)品來(lái)實(shí)現(xiàn),比如oracle的Oracle SQL Loader,導(dǎo)入數(shù)據(jù)特別快。
Hibernate 二級(jí)緩存
二級(jí)緩存需要sessionFactory來(lái)管理,它是進(jìn)初級(jí)的緩存,所有人都可以使用,它是共享的。
二級(jí)緩存比較復(fù)雜,一般用第三方產(chǎn)品。hibernate提供了一個(gè)簡(jiǎn)單實(shí)現(xiàn),用Hashtable做的,只能作為我們的測(cè)試使用,商用還是需要第三方產(chǎn)品。
使用緩存,肯定是長(zhǎng)時(shí)間不改變的數(shù)據(jù),如果經(jīng)常變化的數(shù)據(jù)放到緩存里就沒(méi)有太大意義了。因?yàn)榻?jīng)常變化,還是需要經(jīng)常到數(shù)據(jù)庫(kù)里查詢(xún),那就沒(méi)有必要用緩存了。
hibernate做了一些優(yōu)化,和一些第三方的緩存產(chǎn)品做了集成。老師采用EHCache緩存產(chǎn)品。
和EHCache二級(jí)緩存產(chǎn)品集成:EHCache的jar文件在hibernate的lib里,我們還需要設(shè)置一系列的緩存使用策略,需要一個(gè)配置文件ehcache.xml來(lái)配置。這個(gè)文件放在類(lèi)路徑下。
//默認(rèn)配置,所有的類(lèi)都遵循這個(gè)配置
<defaultCache
//緩存里可以放10000個(gè)對(duì)象
maxElementsInMemory="10000"
//過(guò)不過(guò)期,如果是true就是永遠(yuǎn)不過(guò)期
eternal="false"
//一個(gè)對(duì)象被訪問(wèn)后多長(zhǎng)時(shí)間還沒(méi)有訪問(wèn)就失效(120秒還沒(méi)有再次訪問(wèn)就失效)
timeToIdleSeconds="120"
//對(duì)象存活時(shí)間(120秒),如果設(shè)置永不過(guò)期,這個(gè)就沒(méi)有必要設(shè)了
timeToLiveSeconds="120"
//溢出的問(wèn)題,如果設(shè)成true,緩存里超過(guò)10000個(gè)對(duì)象就保存到磁盤(pán)里
overflowToDisk="true"
/> |
我們也可以對(duì)某個(gè)對(duì)象單獨(dú)配置:
<cache name="com.bjpowernode.hibernate.Student"
maxElementsInMemory="100"
eternal="false"
timeToIdleSeconds="10000"
timeToLiveSeconds="10000"
overflowToDisk="true"
/> |
還需要在hibernate.cfg.xml配置文件配置緩存,讓hibernate知道我們使用的是那個(gè)二級(jí)緩存。
<!-- 配置緩存提供商 -->
<property name="hibernate.cache.provider_class">
org.hibernate.cache.EhCacheProvider</property>
<!-- 啟用二級(jí)緩存,這也是它的默認(rèn)配置 -->
<property name="hibernate.cache.use_second_level_cache">
true</property> |
啟用二級(jí)緩存的配置可以不寫(xiě)的,因?yàn)槟J(rèn)就是true開(kāi)啟二級(jí)緩存。
必須還手動(dòng)指定那些實(shí)體類(lèi)的對(duì)象放到緩存里在hibernate.cfg.xml里:
//在<sessionfactory>標(biāo)簽里,在<mapping>標(biāo)簽后配置
<class-cache class="com.bjpowernode.hibernate.Student"
usage="read-only"/> |
或者在實(shí)體類(lèi)映射文件里:
//在<class>標(biāo)簽里,<id>標(biāo)簽前配置
<cache usage="read-only"/> |
usage屬性表示使用緩存的策略,一般優(yōu)先使用read-only,表示如果這個(gè)數(shù)據(jù)放到緩存里了,則不允許修改,如果修改就會(huì)報(bào)錯(cuò)。這就要注意我們放入緩存的數(shù)據(jù)不允許修改。因?yàn)榉啪彺胬锏臄?shù)據(jù)經(jīng)常修改,也就沒(méi)有必要放到緩存里。
使用read-only策略效率好,因?yàn)椴荒芨木彺妗5强赡軙?huì)出現(xiàn)臟數(shù)據(jù)的問(wèn)題,這個(gè)問(wèn)題解決方法只能依賴(lài)緩存的超時(shí),比如上面我們?cè)O(shè)置了超時(shí)為120秒,120后就可以對(duì)緩存里對(duì)象進(jìn)行修改,而在120秒之內(nèi)訪問(wèn)這個(gè)對(duì)象可能會(huì)查詢(xún)臟數(shù)據(jù)的問(wèn)題,因?yàn)槲覀冃薷膶?duì)象后數(shù)據(jù)庫(kù)里改變了,而緩存卻不能改變,這樣造成數(shù)據(jù)不同步,也就是臟數(shù)據(jù)的問(wèn)題。
第二種緩存策略read-write,當(dāng)持久對(duì)象發(fā)生變化,緩存里就會(huì)跟著變化,數(shù)據(jù)庫(kù)中也改變了。這種方式需要加解鎖,效率要比第一種慢。
還有兩種策略,請(qǐng)看hibernate文檔,最常用還是第一二種策略。
二級(jí)緩存測(cè)試代碼演示:注意上面我們講的兩個(gè)session分別調(diào)用load方法查詢(xún)相同的數(shù)據(jù),第二個(gè)session的load方法還是發(fā)了sql語(yǔ)句到數(shù)據(jù)庫(kù)查詢(xún)數(shù)據(jù),這是因?yàn)橐患?jí)緩存只在當(dāng)前session中共享,也就是說(shuō)一級(jí)緩存不能跨session訪問(wèn)。
//開(kāi)啟二級(jí)緩存,二級(jí)緩存是進(jìn)程級(jí)的緩存,可以共享
//兩個(gè)session分別調(diào)用load方法查詢(xún)相同的實(shí)體對(duì)象
try {
session = HibernateUtils.getSession();
session.beginTransaction();
Student student = (Student)session.load(Student.class, 1);
System.out.println("student.name=" + student.getName());
session.getTransaction().commit();
}catch(Exception e) {
e.printStackTrace();
session.getTransaction().rollback();
}finally {
HibernateUtils.closeSession(session);
}
try {
session = HibernateUtils.getSession();
session.beginTransaction();
Student student = (Student)session.load(Student.class, 1);
//不會(huì)發(fā)出查詢(xún)語(yǔ)句,因?yàn)榕渲枚?jí)緩存,session可以共享二級(jí)緩存中的數(shù)據(jù)
//二級(jí)緩存是進(jìn)程級(jí)的緩存
System.out.println("student.name=" + student.getName());
session.getTransaction().commit();
}catch(Exception e) {
e.printStackTrace();
session.getTransaction().rollback();
}finally {
HibernateUtils.closeSession(session);
} |
如果開(kāi)啟了二級(jí)緩存,那么第二個(gè)session調(diào)用的load方法查詢(xún)第一次查詢(xún)的數(shù)據(jù),是不會(huì)發(fā)出sql語(yǔ)句查詢(xún)數(shù)據(jù)庫(kù)的,而是去二級(jí)緩存中取數(shù)據(jù)。
//開(kāi)啟二級(jí)緩存
//兩個(gè)session分別調(diào)用get方法查詢(xún)相同的實(shí)體對(duì)象
try {
session = HibernateUtils.getSession();
session.beginTransaction();
Student student = (Student)session.get(Student.class, 1);
System.out.println("student.name=" + student.getName());
session.getTransaction().commit();
}catch(Exception e) {
e.printStackTrace();
session.getTransaction().rollback();
}finally {
HibernateUtils.closeSession(session);
}
try {
session = HibernateUtils.getSession();
session.beginTransaction();
Student student = (Student)session.get(Student.class, 1);
//不會(huì)發(fā)出查詢(xún)語(yǔ)句,因?yàn)榕渲枚?jí)緩存,session可以共享二級(jí)緩存中的數(shù)據(jù)
//二級(jí)緩存是進(jìn)程級(jí)的緩存
System.out.println("student.name=" + student.getName());
session.getTransaction().commit();
}catch(Exception e) {
e.printStackTrace();
session.getTransaction().rollback();
}finally {
HibernateUtils.closeSession(session);
} |
注意:二級(jí)緩存必須讓sessionfactory管理,讓sessionfactory來(lái)清除二級(jí)緩存。sessionFactory.evict(Student.class);//清除二級(jí)緩存中所有student對(duì)象,sessionFactory.evict(Student.class,1);//清除二級(jí)緩存中id為1的student對(duì)象。
如果在第一個(gè)session調(diào)用load或get方法查詢(xún)數(shù)據(jù)后,把二級(jí)緩存清除了,那么第二個(gè)session調(diào)用load或get方法查詢(xún)相同的數(shù)據(jù)時(shí),還是會(huì)發(fā)出sql語(yǔ)句查詢(xún)數(shù)據(jù)庫(kù)的,因?yàn)榫彺胬餂](méi)有數(shù)據(jù)只能到數(shù)據(jù)庫(kù)里查詢(xún)。
我們查詢(xún)數(shù)據(jù)后會(huì)默認(rèn)自動(dòng)的放到二級(jí)和一級(jí)緩存里,如果我們想查詢(xún)的數(shù)據(jù)不放到緩存里,也是可以的。也就是說(shuō)我們可以控制一級(jí)緩存和二級(jí)緩存的交換。
session.setCacheMode(CacheMode.IGNORE);禁止將一級(jí)緩存中的數(shù)據(jù)往二級(jí)緩存里放。
還是用上面代碼測(cè)試,在第一個(gè)session調(diào)用load方法前,執(zhí)行session.setCacheMode(CacheMode.IGNORE);這樣load方法查詢(xún)的數(shù)據(jù)不會(huì)放到二級(jí)緩存里。那么第二個(gè)session執(zhí)行l(wèi)oad方法查詢(xún)相同的數(shù)據(jù),會(huì)發(fā)出sql語(yǔ)句到數(shù)據(jù)庫(kù)中查詢(xún),因?yàn)槎?jí)緩存里沒(méi)有數(shù)據(jù),一級(jí)緩存因?yàn)椴煌膕ession不能共享,所以只能到數(shù)據(jù)庫(kù)里查詢(xún)。
上面我們講過(guò)大批量的數(shù)據(jù)添加時(shí)可能會(huì)出現(xiàn)溢出,解決辦法是每當(dāng)天就20個(gè)對(duì)象后就清理一次一級(jí)緩存。如果我們使用了二級(jí)緩存,光清理一級(jí)緩存是不夠的,還要禁止一二級(jí)緩存交互,在save方法前調(diào)用session.setCacheMode(CacheMode.IGNORE)。
二級(jí)緩存也不會(huì)存放普通屬性的查詢(xún)數(shù)據(jù),這和一級(jí)緩存是一樣的,只存放實(shí)體對(duì)象。session級(jí)的緩存對(duì)性能的提高沒(méi)有太大的意義,因?yàn)樯芷谔塘恕?br />
Hibernate 查詢(xún)緩存
一級(jí)緩存和二級(jí)緩存都只是存放實(shí)體對(duì)象的,如果查詢(xún)實(shí)體對(duì)象的普通屬性的數(shù)據(jù),只能放到查詢(xún)緩存里,查詢(xún)緩存還存放查詢(xún)實(shí)體對(duì)象的id。
查詢(xún)緩存的生命周期不確定,當(dāng)它關(guān)聯(lián)的表發(fā)生修改,查詢(xún)緩存的生命周期就結(jié)束。這里表的修改指的是通過(guò)hibernate修改,并不是通過(guò)數(shù)據(jù)庫(kù)客戶(hù)端軟件登陸到數(shù)據(jù)庫(kù)上修改。
hibernate的查詢(xún)緩存默認(rèn)是關(guān)閉的,如果要使用就要到hibernate.cfg.xml文件里配置:
<property name="hibernate.cache.use_query_cache">true</property> |
并且必須在程序中手動(dòng)啟用查詢(xún)緩存,在query接口中的setCacheable(true)方法來(lái)啟用。
//關(guān)閉二級(jí)緩存,沒(méi)有開(kāi)啟查詢(xún)緩存,采用list方法查詢(xún)普通屬性
//同一個(gè)sessin,查詢(xún)兩次
List names = session.createQuery("select s.name from Student s")
.list();
for (int i=0; i<names.size(); i++) {
String name = (String)names.get(i);
System.out.println(name);
}
System.out.println("-----------------------------------------");
//會(huì)發(fā)出sql語(yǔ)句
names = session.createQuery("select s.name from Student s")
.setCacheable(true)
.list();
for (int i=0; i<names.size(); i++) {
String name = (String)names.get(i);
System.out.println(name);
} |
上面代碼運(yùn)行,由于沒(méi)有使用查詢(xún)緩存,而一、二級(jí)緩存不會(huì)緩存普通屬性,所以第二次查詢(xún)還是會(huì)發(fā)出sql語(yǔ)句到數(shù)據(jù)庫(kù)中查詢(xún)。
現(xiàn)在開(kāi)啟查詢(xún)緩存,關(guān)閉二級(jí)緩存,并且在第一次的list方法前調(diào)用setCacheable(true),并且第二次list查詢(xún)前也調(diào)用這句代碼,可以寫(xiě)出下面這樣:
List names = session.createQuery("select s.name from Student s")
.setCacheable(true)
.list(); |
其它代碼不變,運(yùn)行代碼后發(fā)現(xiàn)第二次list查詢(xún)普通屬性沒(méi)有發(fā)出sql語(yǔ)句,也就是說(shuō)沒(méi)有到數(shù)據(jù)庫(kù)中查詢(xún),而是到查詢(xún)緩存中取數(shù)據(jù)。
//開(kāi)啟查詢(xún)緩存,關(guān)閉二級(jí)緩存,采用list方法查詢(xún)普通屬性
//在兩個(gè)session中調(diào)用list方法
try {
session = HibernateUtils.getSession();
session.beginTransaction();
List names = session.createQuery("select s.name from Student s")
.setCacheable(true)
.list();
for (int i=0; i<names.size(); i++) {
String name = (String)names.get(i);
System.out.println(name);
}
session.getTransaction().commit();
}catch(Exception e) {
e.printStackTrace();
session.getTransaction().rollback();
}finally {
HibernateUtils.closeSession(session);
}
System.out.println("----------------------------------------");
try {
session = HibernateUtils.getSession();
session.beginTransaction();
//不會(huì)發(fā)出查詢(xún)語(yǔ)句,因?yàn)椴樵?xún)緩存和session的生命周期沒(méi)有關(guān)系
List names = session.createQuery("select s.name from Student s")
.setCacheable(true)
.list();
for (int i=0; i<names.size(); i++) {
String name = (String)names.get(i);
System.out.println(name);
}
session.getTransaction().commit();
}catch(Exception e) {
e.printStackTrace();
session.getTransaction().rollback();
}finally {
HibernateUtils.closeSession(session);
} |
運(yùn)行結(jié)果是第二個(gè)session發(fā)出的list方法查詢(xún)普通屬性,沒(méi)有發(fā)出sql語(yǔ)句到數(shù)據(jù)庫(kù)中查詢(xún),而是到查詢(xún)緩存里取數(shù)據(jù),這說(shuō)明查詢(xún)緩存和session生命周期沒(méi)有關(guān)系。
//開(kāi)啟緩存,關(guān)閉二級(jí)緩存,采用iterate方法查詢(xún)普通屬性
//在兩個(gè)session中調(diào)用iterate方法查詢(xún) |
運(yùn)行結(jié)果是第二個(gè)session的iterate方法還是發(fā)出了sql語(yǔ)句查詢(xún)數(shù)據(jù)庫(kù),這說(shuō)明iterate迭代查詢(xún)普通屬性不支持查詢(xún)緩存。
//關(guān)閉查詢(xún)緩存,關(guān)閉二級(jí)緩存,采用list方法查詢(xún)實(shí)體對(duì)象
//在兩個(gè)session中調(diào)用list方法查詢(xún) |
運(yùn)行結(jié)果第一個(gè)session調(diào)用list方法查詢(xún)實(shí)體對(duì)象會(huì)發(fā)出sql語(yǔ)句查詢(xún)數(shù)據(jù),因?yàn)殛P(guān)閉了二級(jí)緩存,所以第二個(gè)session調(diào)用list方法查詢(xún)實(shí)體對(duì)象,還是會(huì)發(fā)出sql語(yǔ)句到數(shù)據(jù)庫(kù)中查詢(xún)。
//開(kāi)啟查詢(xún)緩存,關(guān)閉二級(jí)緩存
//在兩個(gè)session中調(diào)用list方法查詢(xún)實(shí)體對(duì)象 |
運(yùn)行結(jié)果第一個(gè)session調(diào)用list方法查詢(xún)實(shí)體對(duì)象會(huì)發(fā)出sql語(yǔ)句查詢(xún)數(shù)據(jù)庫(kù)的。第二個(gè)session調(diào)用list方法查詢(xún)實(shí)體對(duì)象,卻發(fā)出了很多sql語(yǔ)句查詢(xún)數(shù)據(jù)庫(kù),這跟N+1的問(wèn)題是一樣的,發(fā)出了N+1條sql語(yǔ)句。為什么會(huì)出現(xiàn)這樣的情況呢?這是因?yàn)槲覀儸F(xiàn)在查詢(xún)的是實(shí)體對(duì)象,查詢(xún)緩存會(huì)把第一次查詢(xún)的實(shí)體對(duì)象的id放到緩存里,當(dāng)?shù)诙€(gè)session再次調(diào)用list方法時(shí),它會(huì)到查詢(xún)緩存里把id一個(gè)一個(gè)的拿出來(lái),然后到相應(yīng)的緩存里找(先找一級(jí)緩存找不到再找二級(jí)緩存),如果找到了就返回,如果還是沒(méi)有找到,則會(huì)根據(jù)一個(gè)一個(gè)的id到數(shù)據(jù)庫(kù)中查詢(xún),所以一個(gè)id就會(huì)有一條sql語(yǔ)句。
注意:如果配置了二級(jí)緩存,則第一次查詢(xún)實(shí)體對(duì)象后,會(huì)往一級(jí)緩存和二級(jí)緩存里都存放。如果沒(méi)有二級(jí)緩存,則只在一級(jí)緩存里存放。(一級(jí)緩存不能跨session共享)
//開(kāi)啟查詢(xún)緩存,開(kāi)啟二級(jí)緩存
//在兩個(gè)session中調(diào)用list方法查詢(xún)實(shí)體對(duì)象 |
運(yùn)行結(jié)果是第一個(gè)session調(diào)用list方法會(huì)發(fā)出sql語(yǔ)句到數(shù)據(jù)庫(kù)里查詢(xún)實(shí)體對(duì)象,因?yàn)榕渲昧硕?jí)緩存,則實(shí)體對(duì)象會(huì)放到二級(jí)緩存里,因?yàn)榕渲昧瞬樵?xún)緩存,則實(shí)體對(duì)象所有的id放到了查詢(xún)緩存里。第二個(gè)session調(diào)用list方法不會(huì)發(fā)出sql語(yǔ)句,而是到二級(jí)緩存里取數(shù)據(jù)。
查詢(xún)緩存意義不大,查詢(xún)緩存說(shuō)白了就是存放由list方法或iterate方法查詢(xún)的數(shù)據(jù)。我們?cè)诓樵?xún)時(shí)很少出現(xiàn)完全相同條件的查詢(xún),這也就是命中率低,這樣緩存里的數(shù)據(jù)總是變化的,所以說(shuō)意義不大。除非是多次查詢(xún)都是查詢(xún)相同條件的數(shù)據(jù),也就是說(shuō)返回的結(jié)果總是一樣,這樣配置查詢(xún)緩存才有意義。