緩存(Cache):計算機(jī)領(lǐng)域的通用概念缀踪。它介于應(yīng)用程序和永久性數(shù)據(jù)存儲源(如硬盤上的數(shù)據(jù)庫或文件)之間蜻韭。其作用是減少應(yīng)用程序直接讀寫永久性數(shù)據(jù)存儲源的頻率臀栈,從而提高應(yīng)用程序的運(yùn)行性能月腋。緩存中的數(shù)據(jù)是數(shù)據(jù)存儲源中的數(shù)據(jù)的拷貝妄迁。緩存的物理介質(zhì)通常是內(nèi)存疗韵。
Hibernate中提供了兩個級別的緩存:
- 第一級別的緩存是Session級別的緩存兑障,它屬于事務(wù)范圍內(nèi)的緩存。這一級別的緩存由Hibernate進(jìn)行管理蕉汪。
- 第二級別的緩存是SessionFactory級別的緩存流译,它是屬于進(jìn)程范圍內(nèi)的緩存。
SessionFactory級別的緩存
SessionFactory的緩存分為兩類:
- 內(nèi)置緩存:Hibernate自帶的者疤,不可卸載福澡。通常在Hibernate初始化階段,Hibernate會把映射元數(shù)據(jù)和預(yù)定義的SQL語句放到SessionFactory的緩存中驹马,映射元數(shù)據(jù)是映射文件(.hbm.xml)中的數(shù)據(jù)的復(fù)制革砸,該內(nèi)置緩存是只讀的。
- 外置緩存(二級緩存):一個可配置的緩存插件糯累,在默認(rèn)情況下算利,SessionFactory不會啟動這個緩存插件,外置緩存中的數(shù)據(jù)是數(shù)據(jù)庫數(shù)據(jù)的復(fù)制寇蚊,外置緩存的物理介質(zhì)可以是內(nèi)存或硬盤笔时。
適合放入二級緩存的數(shù)據(jù):
- 很少被修改
- 不是很重要的數(shù)據(jù),允許出現(xiàn)偶爾的并發(fā)問題仗岸。
不適合放入二級緩存中的數(shù)據(jù)
- 經(jīng)常被修改
- 財務(wù)數(shù)據(jù)允耿,決不允許出現(xiàn)并發(fā)問題。
- 與其它應(yīng)用程序共享的數(shù)據(jù)扒怖。
二級緩存的并發(fā)訪問策略
兩個并發(fā)的事務(wù)同時訪問持久層的緩存的相同的數(shù)據(jù)時较锡,也有可能出現(xiàn)各種各樣的并發(fā)問題。
二級緩存可以設(shè)定為以下四種類型的并發(fā)訪問策略盗痒,每一種訪問策略對應(yīng)一種事務(wù)的隔離級別蚂蕴。
- 非嚴(yán)格讀寫(Nonstrict-read-write):不保證緩存與數(shù)據(jù)庫中的數(shù)據(jù)的一致性低散,提供Read-uncommited級別的數(shù)據(jù)庫隔離級別。對于極少修改而且允許臟讀的數(shù)據(jù)骡楼,可以采用這種策略熔号。
- 讀寫型(Read-write):提供Read-commited數(shù)據(jù)隔離級別。對于經(jīng)常讀但很少修改的數(shù)據(jù)鸟整,可以使用這種隔離類型引镊,因為它可以防止臟讀。
- 事務(wù)性(Transactional):僅在受管理環(huán)境下適用篮条,它提供了Repeatable-read事務(wù)隔離級別弟头。對于經(jīng)常讀但很少修改的數(shù)據(jù),可以采用這種隔離類型涉茧,因為它可以防止臟讀和不可重復(fù)讀赴恨。
- 只讀型(Read-only):提供Serializable數(shù)據(jù)庫隔離類型,對于從來不會被修改的數(shù)據(jù)伴栓,可以采用這種訪問策略伦连。
配置進(jìn)程范圍內(nèi)的二級緩存
- 選擇合適范圍的緩存插件:EHCache(jar包和配置),并編譯器配置文件挣饥。
- 在Hibernate的配置文件中啟用二級緩存除师,并指定EHCache對應(yīng)的緩存適配器。
//啟動Hibernate的二級緩存
<property name="cache.use_second_level_cache">true</property>
//配置Hibernate二級緩存使用的產(chǎn)品
<property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>
//配置對那些類使用Hibernate的二級緩存
<class-cache usage="read-write" class="com.hibernate.unionsubclass.Student"></class-cache>
也可以在.hbm.xml類中配置二級緩存扔枫。
- 選擇需要使用二級緩存的持久化類汛聚,設(shè)置它的二級緩存的并發(fā)訪問策略
- <class>元素的cache子元素表明Hibernate會緩存對象的簡單屬性,但不會緩存集合屬性短荐,如果希望緩存集合屬性倚舀,必須在<set>元素中加入<cache>子元素。
- 在hibernate配置文件中通過<class-cache/>結(jié)點(diǎn)配置使用緩存忍宋。
設(shè)置集合緩存
我們看下面的代碼
SessionFactory sessionFactory = null;
Configuration cfg = new Configuration().configure();
sessionFactory = cfg.buildSessionFactory();
Session session = sessionFactory.openSession();
Transaction transaction = session.beginTransaction();
Category category = session.get(Category.class, 42);
System.out.println(category.getItems().size());
transaction.commit();
session.close();
session = sessionFactory.openSession();
transaction = session.beginTransaction();
Category category2 = session.get(Category.class, 42);
System.out.println(category2.getItems().size());
transaction.commit();
session.close();
sessionFactory.close();
運(yùn)行后發(fā)現(xiàn)生成了四條SQL語句痕貌。此時我們給Category類添加入二級緩存。
<class-cache usage="read-write" class="com.hibernate.n2n.Category"></class-cache>
發(fā)現(xiàn)少了一條SQL語句糠排,但在獲取items的時候還是兩條SQL語句舵稠,此時我們需要將Item類添加到二級緩存中。
<collection-cache usage="read-write" collection="com.hibernate.n2n.Category.items"></collection-cache>
此時發(fā)現(xiàn)又是四條SQL語句入宦。因為我們僅僅緩存了items集合類哺徊,沒有緩存其中的類型。
<class-cache usage="read-write" class="com.hibernate.n2n.Item"></class-cache>
這個時候控制臺只輸出了兩條SQL語句乾闰。
集合緩存也可以在.hbm.xml中配置
<set name="items" table="category_item">
<cache usage="read-write"/>
<key>
<column name="Category_Id"/>
</key>
<many-to-many class="Item" column="Item_Id"/>
</set>
注意落追,還需要在集合包含的類中配置緩存。
ehcache.xml文件
<diskStore path="java.io.tmpdir"/>
diskStore表示磁盤的存儲路徑涯肩。當(dāng)有大量的數(shù)據(jù)的時候我們需要設(shè)置臨界值轿钠,如果低于這個臨界值巢钓,則將數(shù)據(jù)存入到內(nèi)存中。高于這個數(shù)據(jù)則放到磁盤上疗垛。該路徑表示磁盤的位置症汹。
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
/>
默認(rèn)的存儲策略。
<cache name="sampleCache1"
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="300"
timeToLiveSeconds="600"
overflowToDisk="true"
/>
設(shè)置具體的命名緩存數(shù)據(jù)的策略继谚,每個命名緩存代表一個緩存區(qū)域烈菌。
緩存區(qū)域(region):一個具有名稱的緩存塊阵幸,可以給每個緩存塊設(shè)置不同的緩存策略花履。如果沒有設(shè)置任何緩存區(qū)域,則所有的緩存對象將使用默認(rèn)的緩存策略挚赊。
配置項解釋:
name:設(shè)置緩存的名字诡壁,它的取值為類的全限定名或類的集合名字。
maxElementInMemory:在內(nèi)存中最多可以存儲對象的數(shù)量荠割。
eternal:設(shè)置對象在緩存中是否為永久的妹卿,true表示永不過期。此時將忽略timeToIdleSeconds和timeToLiveSecond蔑鹦,默認(rèn)值為false夺克。
timeToIdleSeconds:設(shè)置對象空閑的最長時間,單位是秒嚎朽。如果超過這個時間铺纽,對象將從緩沖中清除。
timeToLiveSecond:設(shè)置對象的最長生存時間哟忍,超過這個時間狡门,對象過期。如果值為0表示對象可以永久的存在緩存中锅很。
overflowToDisk:設(shè)置內(nèi)存中的緩存中的對象的數(shù)量達(dá)到上限后其馏,是否把溢出的對象寫到基于硬盤的緩存中。
查詢緩存
當(dāng)執(zhí)行以下的代碼
Query query = session.createQuery("from Category ");
List<Category>categories = query.list();
System.out.println(categories.size());
categories = query.list();
System.out.println(categories.size());
我們會發(fā)現(xiàn)執(zhí)行了兩條SQL爆安。說明緩存沒有起作用叛复。
此時我們需要設(shè)置查詢緩存。
默認(rèn)情況下設(shè)置的緩存對HQL和QBC查詢是無效的扔仓。通過以下方式配置
在Hibernate配置文件中聲明開啟查詢緩存
<property name="cache.use_query_cache">true</property>
調(diào)用Query或Criteria的setCacheable方法褐奥,設(shè)置為true。
query.setCacheable(true);
注意:查詢緩存依賴二級緩存当辐。
時間戳緩存
時間戳緩存區(qū)域存放了對于查詢結(jié)果相關(guān)的表進(jìn)行插入抖僵,更新,刪除操作的時間戳缘揪。Hibernate通過時間戳緩存區(qū)域判斷緩存的查詢結(jié)果是否過期耍群,其運(yùn)行過程如下:
- T1時刻執(zhí)行查詢操作义桂,把查詢結(jié)果放在QueryCache區(qū)域,記錄該區(qū)域的時間戳為T1蹈垢。
- T2時刻對查詢結(jié)果相關(guān)的表進(jìn)行更新操作慷吊,Hibernate把T2時刻放在UpdateTimestampCache區(qū)域。
- T3時刻在執(zhí)行查詢結(jié)果前曹抬,先比較QueryCache區(qū)域的時間戳和UpdateTimestampCache區(qū)域的時間戳溉瓶,若T2>T1,那么丟棄原先存放在QueryCache區(qū)域的查詢結(jié)果谤民,重新懂啊數(shù)據(jù)庫中查詢數(shù)據(jù)堰酿,再把結(jié)果存放到QueryCache區(qū)域;若T2<T1张足,直接從QueryCache區(qū)域取出查詢結(jié)果触创。
Query接口的iterator方法
當(dāng)遍歷訪問結(jié)果集的時候,該方法先到Session緩存及二級緩存中查看是否存在特定OID的對象为牍,如果存在哼绑,就直接返回該對象。如果不存在該對象碉咆,則通過相應(yīng)的SQL select語句到數(shù)據(jù)庫中加載特定的實體類對象抖韩。
大多數(shù)情況下應(yīng)使用list方法執(zhí)行查詢操作。iterator方法僅在滿足以下條件的場景中適合使用疫铜,可以稍微提高查詢的性能:
- 要查詢的數(shù)據(jù)表中含有大量的字段
- 啟用了二級緩存茂浮,且二級緩存可能已經(jīng)包含了待查詢的對象。