緩存
計(jì)算機(jī)領(lǐng)域非常通用的概念,它介于應(yīng)用程序和永久性數(shù)據(jù)存儲(chǔ)源(如硬盤上的文件或者數(shù)據(jù)庫)之間穿挨,其作用是降低應(yīng)用程序直接讀寫永久性數(shù)據(jù)存儲(chǔ)源的頻率月弛,從而提高應(yīng)用的運(yùn)行性能肴盏,緩存中的數(shù)據(jù)是數(shù)據(jù)存儲(chǔ)源中數(shù)據(jù)的拷貝。
緩存的物理介質(zhì)通常是內(nèi)存帽衙。
Hibernate提供的兩種級(jí)別的緩存:
- 第一級(jí)別的緩存:Session級(jí)別的緩存
它是屬于事務(wù)范圍的緩存菜皂,這一級(jí)別的緩存是由Hibernate管理的。 - 第二級(jí)別的緩存:SessionFactory級(jí)別的緩存
它是屬于進(jìn)程范圍的緩存厉萝。
SessionFactory級(jí)別的緩存
SessionFactory級(jí)別的緩存可以分為兩類
內(nèi)置緩存
Hibernate自帶的恍飘,不可卸載的,通常在Hibernate的初始化階段谴垫,Hibernate會(huì)把映射元數(shù)據(jù)和預(yù)定義的SQL語句放到SessionFactory的緩存中章母,映射元數(shù)據(jù)是映射文件中的數(shù)據(jù)(.hbm.xml文件中的數(shù)據(jù))的復(fù)制,該內(nèi)置緩存是只讀的翩剪。外置緩存(二級(jí)緩存)
一個(gè)可配置的緩存插件胳施。在默認(rèn)情況下,SessionFactory不會(huì)啟用這個(gè)緩存插件肢专,外置緩存中的數(shù)據(jù)是數(shù)據(jù)庫數(shù)據(jù)的復(fù)制,外置緩存的物理介質(zhì)可以是內(nèi)存或硬盤焦辅。
Hibernate的二級(jí)緩存
適合放入二級(jí)緩存中的數(shù)據(jù):
- 很少被修改
- 不是很重要的數(shù)據(jù)博杖,允許出現(xiàn)偶爾的并發(fā)問題
不適合放入二級(jí)緩存中的數(shù)據(jù):
- 經(jīng)常被修改
- 財(cái)務(wù)數(shù)據(jù),絕對(duì)不允許出現(xiàn)并發(fā)問題
- 與其他應(yīng)用程序共享的數(shù)據(jù)
二級(jí)緩存的并發(fā)訪問策略
兩個(gè)并發(fā)的事務(wù)同時(shí)訪問持久層的緩存的相同數(shù)據(jù)時(shí)筷登,也有可能出現(xiàn)各類并發(fā)問題剃根。
二級(jí)緩存可以設(shè)定以下4種類型的并發(fā)訪問策略,每一種訪問策略對(duì)應(yīng)一種事務(wù)隔離級(jí)別
- 非嚴(yán)格讀寫(Nostrict-read-write)
不保證緩存與數(shù)據(jù)庫中數(shù)據(jù)的一致性前方,提供Read Uncommited事務(wù)隔離級(jí)別狈醉,對(duì)于極少被修改,而且允許臟讀的數(shù)據(jù)惠险,可以采用這種策略苗傅。 -
讀寫型(Read-write)
提供Read Commited數(shù)據(jù)隔離級(jí)別,對(duì)于經(jīng)常讀但是很少被修改的數(shù)據(jù)班巩,可以采用這種隔離類型渣慕。因?yàn)樗梢苑乐古K讀。 - 事務(wù)型(Transactional)
僅在受管理環(huán)境下適用抱慌,它提供了Repeatable Read事務(wù)隔離級(jí)別膝蜈,對(duì)于經(jīng)常讀但是很少被修改的數(shù)據(jù)腋粥,揭陽采用這種隔離類型,因?yàn)樗梢苑乐古K讀和不可重復(fù)讀。 - 只讀型(Read-Only)
提供了Serializable數(shù)據(jù)隔離級(jí)別肺素,對(duì)于從來不會(huì)被修改的數(shù)據(jù),可以采用這種訪問策略大猛。
配置進(jìn)程范圍內(nèi)的二級(jí)緩存的步驟
- 加入二級(jí)緩存插件的jar包及配置文件
jar包有:ehcache-2.10.6.jar
,hibernate-ehcache-5.4.3.Final.jar
,slf4j-api-1.7.25.jar
。
配置文件ehcache.xml如下:
<ehcache>
<diskStore path="java.io.tmpdir"/>
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
/>
<cache name="sampleCache1"
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="300"
timeToLiveSeconds="600"
overflowToDisk="true"
/>
<cache name="sampleCache2"
maxElementsInMemory="1000"
eternal="true"
timeToIdleSeconds="0"
timeToLiveSeconds="0"
overflowToDisk="false"
/>
</ehcache>
- 配置Hibernate.cfg.cml文件
配置啟用hibernate的二級(jí)緩存:<property name="cache.use_second_level_cache">true</property>
配置Hibernate二級(jí)緩存使用的產(chǎn)品:<property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.internal.EhcacheRegionFactory</property>
配置對(duì)哪些類使用Hibernate的二級(jí)緩存:<class-cache class="com.cerr.hibernate.entities.Employee" usage="read-write" />
實(shí)際上也可以在.hbm.xml
文件中配置對(duì)哪些類使用二級(jí)緩存及二級(jí)緩存的策略兰迫。例如在Employee.hbm.xml
文件的class
節(jié)點(diǎn)里面加上<cache usage="read-write"/>
。
設(shè)置二級(jí)緩存之后码秉,測(cè)試如下:
@org.junit.Test
public void testHibernateSecondLevelCache(){
Employee employee = session.get(Employee.class,1);
System.out.println(employee.getName());
//關(guān)閉session后重新連接
transaction.commit();
session.close();
session = sessionFactory.openSession();
transaction = session.beginTransaction();
Employee employee1 = session.get(Employee.class,1);
System.out.println(employee1.getName());
}
在沒設(shè)置二級(jí)緩存的時(shí)候逮矛,該情形會(huì)發(fā)送兩次sql語句,因?yàn)樵谥虚g關(guān)閉了session
后再重新開啟转砖。而我們已經(jīng)設(shè)置了二級(jí)緩存须鼎,因此只會(huì)發(fā)送一條sql語句。
集合級(jí)別的二級(jí)緩存的配置
- 配置對(duì)集合使用二級(jí)緩存府蔗,例如:
<collection-cache collection="com.cerr.hibernate.entities.Department.emps" usage="read-write" />
晋控,也可以在hbm.xml
文件中使用<cache>
標(biāo)簽進(jìn)行配置。 - 還需要配置集合中的元素對(duì)應(yīng)的持久化類也使用二級(jí)緩存姓赤,否則將會(huì)多出n條SQL語句赡译。
以Department為例,配置文件hibernate.cfg.xml
如下:
<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
"-//Hibernate/Hibernate Configuration DTD//EN"
"http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
<hibernate-configuration>
<session-factory>
<!-- 配置連接數(shù)據(jù)庫的基本信息 -->
<property name="connection.driver_class">com.mysql.jdbc.Driver</property>
<property name="connection.url">jdbc:mysql://localhost:3306/hibernate5</property>
<property name="connection.username">root</property>
<property name="connection.password">root</property>
<!-- 配置hibernate的基本信息-->
<!-- hibernate所使用的的數(shù)據(jù)庫方言 -->
<property name="dialect">org.hibernate.dialect.MySQL5Dialect</property>
<!-- 執(zhí)行操作時(shí)是否在控制臺(tái)打印SQL-->
<property name="show_sql">true</property>
<!-- 是否對(duì)SQL進(jìn)行格式化-->
<property name="format_sql">true</property>
<!-- 指定生成數(shù)據(jù)表的策略-->
<property name="hibernate.hbm2ddl.auto">update</property>
<!-- 設(shè)置hibernate的事務(wù)隔離級(jí)別 -->
<property name="connection.isolation">2</property>
<property name="use_identifier_rollback">true</property>
<!-- 配置C3P0數(shù)據(jù)源 -->
<property name="hibernate.connection.provider_class">org.hibernate.c3p0.internal.C3P0ConnectionProvider</property>
<property name="c3p0.max_size">10</property>
<property name="c3p0.min_size">5</property>
<property name="c3p0.acquire_increment">2</property>
<property name="c3p0.idle_test_period">2000</property>
<property name="c3p0.timeout">2000</property>
<property name="c3p0.max_statements">10</property>
<!-- 設(shè)定JDBC的Statement讀取數(shù)據(jù)的時(shí)候每次從數(shù)據(jù)庫中取出的記錄條數(shù)-->
<property name="hibernate.jdbc.fetch_size">100</property>
<!-- 設(shè)定對(duì)數(shù)據(jù)庫進(jìn)行批量刪除不铆,更新蝌焚,插入的時(shí)候批次的大小 -->
<property name="hibernate.jdbc.batch_size">30</property>
<!-- 啟用二級(jí)緩存-->
<property name="cache.use_second_level_cache">true</property>
<!-- 配置使用的二級(jí)緩存的產(chǎn)品 -->
<property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.internal.EhcacheRegionFactory</property>
<mapping resource="com/cerr/hibernate/entities/Employee.hbm.xml"/>
<mapping resource="com/cerr/hibernate/entities/Department.hbm.xml"/>
<class-cache class="com.cerr.hibernate.entities.Employee" usage="read-write" />
<class-cache class="com.cerr.hibernate.entities.Department" usage="read-write" />
<!-- 對(duì)Department的集合使用二級(jí)緩存-->
<collection-cache collection="com.cerr.hibernate.entities.Department.emps" usage="read-write" />
</session-factory>
</hibernate-configuration>
測(cè)試:
@org.junit.Test
public void testCollectionSecondLevelCache(){
Department department = session.get(Department.class,1);
System.out.println(department.getName());
System.out.println(department.getEmps().size());
transaction.commit();
session.close();
session = sessionFactory.openSession();
transaction = session.beginTransaction();
Department department1 = session.get(Department.class,1);
System.out.println(department1.getName());
System.out.println(department1.getEmps().size());
}
對(duì)ehcache.xml文件的解析
<ehcache>
<!-- 指定一個(gè)目錄:當(dāng)EHCache把數(shù)據(jù)寫到硬盤上時(shí),將把數(shù)據(jù)寫到這個(gè)目錄下 -->
<diskStore path="java.io.tmpdir"/>
<!-- 設(shè)置緩存的默認(rèn)數(shù)據(jù)過期策略 -->
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="true"
/>
<!-- 設(shè)置具體的命名緩存的數(shù)據(jù)過期策略誓斥,每個(gè)命名緩存代表一個(gè)緩存區(qū)域-->
<cache name="com.cerr.hibernate.entities.Employee"
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="300"
timeToLiveSeconds="600"
overflowToDisk="true"
/>
<cache name="com.cerr.hibernate.entities.Department"
maxElementsInMemory="1000"
eternal="true"
timeToIdleSeconds="0"
timeToLiveSeconds="0"
overflowToDisk="false"
/>
</ehcache>
關(guān)于幾個(gè)標(biāo)簽
-
<diskStore>
標(biāo)簽
指定一個(gè)目錄:當(dāng)EHCache把數(shù)據(jù)寫到硬盤上時(shí)只洒,將把數(shù)據(jù)寫到這個(gè)目錄下 -
<defaultCache>
標(biāo)簽
設(shè)置緩存的默認(rèn)數(shù)據(jù)過期策略 -
<cache>
標(biāo)簽
設(shè)置具體的命名緩存的數(shù)據(jù)過期策略,每個(gè)命名緩存代表一個(gè)緩存區(qū)域
Hibernate在不同的緩存區(qū)域保存不同的類/集合
對(duì)于類而言劳坑,區(qū)域的名稱是類名毕谴,例如:com.cerr.domain.Customer
對(duì)于集合而言,區(qū)域的名稱是類名加屬性名距芬,例如:com.cerr.domain.Customer.orders
cache元素的屬性
-
name
:設(shè)置緩存的名字涝开,它的取值為類的全限定名或類的集合的名字。 -
maxInMemory
:設(shè)置基于內(nèi)存的緩存中可存放的對(duì)象的最大數(shù)目框仔。 -
etemal
:設(shè)置對(duì)象是否為永久的舀武,true
表示永不過期,此時(shí)將忽略timeToldleSeconds
和timeToLiveSeconds
屬性:默認(rèn)是false
离斩。 -
timeToldleSeconds
:設(shè)置對(duì)象空閑最長時(shí)間奕剃,以秒為單位,超過這個(gè)時(shí)間捐腿,對(duì)象過期纵朋。當(dāng)對(duì)象過期時(shí),EHCache會(huì)把它從緩存中清除茄袖。如果此值為0操软,表示對(duì)象可以無限期地處于空閑狀態(tài)。 -
timeToLiveSeconds
:設(shè)置對(duì)象生存最長時(shí)間宪祥,超過這個(gè)時(shí)間聂薪,對(duì)象過期家乘。如果此值為0,表示對(duì)象可以無限期地存在于緩存中藏澳,該屬性值必須大于或等于timeToldleSeconds
屬性值仁锯。 -
overflowToDisk
:設(shè)置基于內(nèi)在的緩存中的對(duì)象數(shù)目達(dá)到上限后,是否把溢出的對(duì)象寫在基于硬盤的緩存中翔悠。
查詢緩存
默認(rèn)情況下业崖,設(shè)置的緩存對(duì)HQL及QBC查詢是無效的,但可以通過設(shè)置查詢緩存來使其支持這兩種查詢蓄愁。
設(shè)置的步驟:
- 在
hibernate.cfg.xml
文件中聲明開啟查詢緩存双炕,例如:<property name="cache.use_query_cache">true</property>
- 調(diào)用
Query
或Criteria
的setCacheable(true)
。
注意:查詢緩存依賴于二級(jí)緩存撮抓,即若要使用查詢緩存妇斤,必須先配置二級(jí)緩存,否則無法使用丹拯。
Demo如下:
@org.junit.Test
public void testQueryCache(){
Query query = session.createQuery("FROM Employee ");
//設(shè)置查詢緩存
query.setCacheable(true);
List<Employee> employees = query.list();
System.out.println(employees.size());
employees = query.list();
System.out.println(employees.size());
}
時(shí)間戳緩存區(qū)域
時(shí)間戳緩存區(qū)域存放了對(duì)于查詢結(jié)果相關(guān)的表進(jìn)行插入站超,更新或刪除操作的時(shí)間戳,Hibernate通過時(shí)間戳緩存區(qū)域來判斷被緩存的查詢結(jié)果是否過期乖酬,其運(yùn)行過程如下:
- T1時(shí)刻執(zhí)行查詢操作死相,把查詢結(jié)果存放在QueryCache區(qū)域,記錄該區(qū)域的時(shí)間戳為T1.
- T2時(shí)刻對(duì)查詢結(jié)果相關(guān)的表進(jìn)行更新操作剑刑,Hibernate把T2時(shí)刻存放在UpdateTimestampCache區(qū)域。
- T3時(shí)刻執(zhí)行查詢結(jié)果前双肤,先比較QueryCache區(qū)域的時(shí)間戳和UpdateTimestampCache區(qū)域的時(shí)間戳施掏,若T2>T1,那么就丟棄原先存放在QueryCache區(qū)域的查詢結(jié)果茅糜,重新到數(shù)據(jù)庫中查詢數(shù)據(jù)七芭,再把結(jié)果存放到QueryCache區(qū)域;若T2<T1蔑赘,直接從QueryCache中獲得查詢結(jié)果狸驳。
Query接口的iterate()方法
同list()
一樣也能執(zhí)行查詢操作。
list()
執(zhí)行的SQL語句包含實(shí)體類對(duì)應(yīng)的數(shù)據(jù)表的所有字段缩赛。
iterator()
執(zhí)行的SQL語句中僅包含實(shí)體類對(duì)應(yīng)的數(shù)據(jù)表的ID字段耙箍。
當(dāng)遍歷訪問結(jié)果集時(shí),該方法先到Session緩存及二級(jí)緩存中查看是否存在特定OID的對(duì)象酥馍,如果存在辩昆,就直接返回該對(duì)象,如果不存在該對(duì)象就通過相應(yīng)的SQL Select語句到數(shù)據(jù)庫中加載特定的實(shí)體對(duì)象
大多數(shù)情況下旨袒,應(yīng)考慮使用list()
執(zhí)行查詢操作汁针,iterator()
僅僅在滿足以下條件的場(chǎng)合术辐,可以稍微提高查詢性能:
- 要查詢的數(shù)據(jù)表中包含大量字段
- 啟用了二級(jí)緩存,且二級(jí)緩存中可能已經(jīng)包含了待查詢的對(duì)象施无。