一、Hibernate 二級緩存
1.Hibernate 二級緩存是 SessionFactory 級別的緩存。
2.二級緩存分為兩類:
(1)Hibernate內置二級緩存
(2)外置緩存,可配置的,可插撥的,外置緩存中的數據是數據庫數據的復制。
3.二級緩存的並發訪問策略
(1)兩個並發的事務同時訪問持久層的緩存的相同數據時,也有可能出現並發問題。
(2)二級緩存可以設定以下 4 中並發訪問策略,每一種對應一種事務隔離級別。
4.Hibernate 二級緩存是進程或集群范圍內的緩存。是可配置的的插件。這裡以 Ehcache 為例。不支持事務型的並發訪問策略。
5.配置 Ehcache
(1)添加 Jar 包
(2)在 Hibernate 配置文件中啟用二級緩存並指定適配的緩存適配器。
在 <session-factory> 元素內添加:
<property name="cache.use_second_level_cache">true</property> <property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>
(3)在 Hibernate 配置文件中配置需要使用二級緩存的持久化類,並設置它的二級緩存的並發訪問策略。如:
<class-cache class="com.solverpeng.hql.Department" usage="read-write"/>
需要注意的是:<session-factory> 元素內子元素的順序:property*, mapping*, (class-cache|collection-cache)*, event*, listener*,class-cache 元素必須位於 mapping 節點後。
(4)或者也可以在 hbm 文件中配置,如:
<hibernate-mapping> <class name="com.solverpeng.hql.Employee" table="employee" schema="hibernate"> <cache usage="read-write"/> <id name="empId" column="emp_id"/> <property name="empName" column="emp_name"/> <property name="salary" column="salary"/> <many-to-one name="dept" class="com.solverpeng.hql.Department"> <column name="dept_id_fk" not-null="true"/> </many-to-one> </class> </hibernate-mapping>
6. 測試 Hibernate 二級緩存
(1)查詢單個對象緩存
@Test public void testSecondCache() { Employee o = (Employee) session.get(Employee.class, 6); System.out.println(o); transaction.commit(); session.close(); session = factory.openSession(); transaction = session.beginTransaction(); Employee o1 = (Employee) session.get(Employee.class, 6); System.out.println(o1); }
查詢同一個對象兩次,中間關閉 Session 再開啟。
沒有配置二級緩存:
Hibernate: select employee0_.emp_id as emp1_1_0_, employee0_.emp_name as emp2_1_0_, employee0_.salary as salary3_1_0_, employee0_.dept_id_fk as dept4_1_0_ from hibernate.employee employee0_ where employee0_.emp_id=? com.solverpeng.hql.Employee@40dd58f3 Hibernate: select employee0_.emp_id as emp1_1_0_, employee0_.emp_name as emp2_1_0_, employee0_.salary as salary3_1_0_, employee0_.dept_id_fk as dept4_1_0_ from hibernate.employee employee0_ where employee0_.emp_id=? com.solverpeng.hql.Employee@40dd58f3
結果:
發送了2條 SQL 語句。
配置二級緩存後(可以在Hibernate 配置文件中配置,也可以在 Employee hbm 配置文件中配置):
Hibernate: select employee0_.emp_id as emp1_1_0_, employee0_.emp_name as emp2_1_0_, employee0_.salary as salary3_1_0_, employee0_.dept_id_fk as dept4_1_0_ from hibernate.employee employee0_ where employee0_.emp_id=? com.solverpeng.hql.Employee@40dd58f3 com.solverpeng.hql.Employee@40dd58f3
結果:
只發送了1條 SQL 語句。
(2)集合緩存
@Test public void testCollectionSecondLevelCache() { Department department = (Department) session.get(Department.class, 6); System.out.println(department.getDeptName()); System.out.println(department.getEmps().size()); transaction.commit(); session.close(); session = factory.openSession(); transaction = session.beginTransaction(); Department department2 = (Department) session.get(Department.class, 6); System.out.println(department2.getDeptName()); System.out.println(department2.getEmps().size()); }
在沒有配置二級緩存的情況下:
Hibernate: select department0_.dept_id as dept1_0_0_, department0_.dept_name as dept2_0_0_ from hibernate.department department0_ where department0_.dept_id=? dept-aa Hibernate: select emps0_.dept_id_fk as dept4_0_1_, emps0_.emp_id as emp1_1_1_, emps0_.emp_id as emp1_1_0_, emps0_.emp_name as emp2_1_0_, emps0_.salary as salary3_1_0_, emps0_.dept_id_fk as dept4_1_0_ from hibernate.employee emps0_ where emps0_.dept_id_fk=? 3 Hibernate: select department0_.dept_id as dept1_0_0_, department0_.dept_name as dept2_0_0_ from hibernate.department department0_ where department0_.dept_id=? dept-aa Hibernate: select emps0_.dept_id_fk as dept4_0_1_, emps0_.emp_id as emp1_1_1_, emps0_.emp_id as emp1_1_0_, emps0_.emp_name as emp2_1_0_, emps0_.salary as salary3_1_0_, emps0_.dept_id_fk as dept4_1_0_ from hibernate.employee emps0_ where emps0_.dept_id_fk=? 3
結果:
查詢了兩次 Department,兩次Employee
只配置 Department 二級緩存:
<class-cache class="com.solverpeng.hql.Department" usage="read-write"/>
Hibernate: select department0_.dept_id as dept1_0_0_, department0_.dept_name as dept2_0_0_ from hibernate.department department0_ where department0_.dept_id=? dept-aa Hibernate: select emps0_.dept_id_fk as dept4_0_1_, emps0_.emp_id as emp1_1_1_, emps0_.emp_id as emp1_1_0_, emps0_.emp_name as emp2_1_0_, emps0_.salary as salary3_1_0_, emps0_.dept_id_fk as dept4_1_0_ from hibernate.employee emps0_ where emps0_.dept_id_fk=? 3 dept-aa Hibernate: select emps0_.dept_id_fk as dept4_0_1_, emps0_.emp_id as emp1_1_1_, emps0_.emp_id as emp1_1_0_, emps0_.emp_name as emp2_1_0_, emps0_.salary as salary3_1_0_, emps0_.dept_id_fk as dept4_1_0_ from hibernate.employee emps0_ where emps0_.dept_id_fk=? 3
結果:
只查詢了一次 Department,2次Employee。
說明:
開啟 Department 二級緩存後,會對 Department 進行緩存,而與其關聯的 emps 不會進行緩存。
配置 Department 二級緩存,同時配置關聯的 emps 緩存。
<class-cache class="com.solverpeng.hql.Department" usage="read-write"/> <collection-cache collection="com.solverpeng.hql.Department.emps" usage="read-write"/>
Hibernate: select department0_.dept_id as dept1_0_0_, department0_.dept_name as dept2_0_0_ from hibernate.department department0_ where department0_.dept_id=? dept-aa Hibernate: select emps0_.dept_id_fk as dept4_0_1_, emps0_.emp_id as emp1_1_1_, emps0_.emp_id as emp1_1_0_, emps0_.emp_name as emp2_1_0_, emps0_.salary as salary3_1_0_, emps0_.dept_id_fk as dept4_1_0_ from hibernate.employee emps0_ where emps0_.dept_id_fk=? 3 ----------------------------- dept-aa Hibernate: select employee0_.emp_id as emp1_1_0_, employee0_.emp_name as emp2_1_0_, employee0_.salary as salary3_1_0_, employee0_.dept_id_fk as dept4_1_0_ from hibernate.employee employee0_ where employee0_.emp_id=? Hibernate: select employee0_.emp_id as emp1_1_0_, employee0_.emp_name as emp2_1_0_, employee0_.salary as salary3_1_0_, employee0_.dept_id_fk as dept4_1_0_ from hibernate.employee employee0_ where employee0_.emp_id=? Hibernate: select employee0_.emp_id as emp1_1_0_, employee0_.emp_name as emp2_1_0_, employee0_.salary as salary3_1_0_, employee0_.dept_id_fk as dept4_1_0_ from hibernate.employee employee0_ where employee0_.emp_id=? 3
結果:
發送了更多的查詢 Employee 的SQL。
說明:
開啟集合的二級緩存後,此時會緩存集合中對象的 id ,而不會對集合中的對象進行緩存。若想緩存,需要關聯的集合中的對象也開啟二級緩存。如:
<class-cache class="com.solverpeng.hql.Department" usage="read-write"/> <collection-cache collection="com.solverpeng.hql.Department.emps" usage="read-write"/> <class-cache class="com.solverpeng.hql.Employee" usage="read-write"/>
Hibernate: select department0_.dept_id as dept1_0_0_, department0_.dept_name as dept2_0_0_ from hibernate.department department0_ where department0_.dept_id=? dept-aa Hibernate: select emps0_.dept_id_fk as dept4_0_1_, emps0_.emp_id as emp1_1_1_, emps0_.emp_id as emp1_1_0_, emps0_.emp_name as emp2_1_0_, emps0_.salary as salary3_1_0_, emps0_.dept_id_fk as dept4_1_0_ from hibernate.employee emps0_ where emps0_.dept_id_fk=? 3 ----------------------------- dept-aa 3
結果:
除 Department 外,關聯的 Employee 也被緩存了。
(3)查詢緩存(針對 HQL、QBC)
在二級緩存開啟的情況下,HQL、QBC 也不能對查詢進行緩存。
@Test public void testQueryCache() { Query query = session.createQuery("from Employee "); List<Employee> list = query.list(); System.out.println(list.size()); List<Employee> emps2 = query.list(); System.out.println(emps2.size()); }
Hibernate: select employee0_.emp_id as emp1_1_, employee0_.emp_name as emp2_1_, employee0_.salary as salary3_1_, employee0_.dept_id_fk as dept4_1_ from hibernate.employee employee0_ 12 Hibernate: select employee0_.emp_id as emp1_1_, employee0_.emp_name as emp2_1_, employee0_.salary as salary3_1_, employee0_.dept_id_fk as dept4_1_ from hibernate.employee employee0_ 12
開啟查詢緩存:
<property name="hibernate.cache.use_query_cache">true</property>
@Test public void testQueryCache() { Query query = session.createQuery("from Employee "); query.setCacheable(true); List<Employee> list = query.list(); System.out.println(list.size()); List<Employee> emps2 = query.list(); System.out.println(emps2.size()); }
Hibernate: select employee0_.emp_id as emp1_1_, employee0_.emp_name as emp2_1_, employee0_.salary as salary3_1_, employee0_.dept_id_fk as dept4_1_ from hibernate.employee employee0_ 12 12
(4)時間戳緩存區域:時間戳緩存區存放了對於查詢結果相關的表進行插入、更新或刪除操作的時間戳。Hibernate通過時間戳緩存區來判定被緩存的查詢結果是否過期。
@Test public void testTimStampCache() { Query query = session.createQuery("from Employee "); query.setCacheable(true); List<Employee> list = query.list(); System.out.println(list.size()); Employee employee = (Employee) session.get(Employee.class, 6); employee.setEmpName("emp@@"); List<Employee> emps2 = query.list(); System.out.println(emps2.size()); }
Hibernate: select employee0_.emp_id as emp1_1_, employee0_.emp_name as emp2_1_, employee0_.salary as salary3_1_, employee0_.dept_id_fk as dept4_1_ from hibernate.employee employee0_ 12 Hibernate: update hibernate.employee set emp_name=?, salary=?, dept_id_fk=? where emp_id=? Hibernate: select employee0_.emp_id as emp1_1_, employee0_.emp_name as emp2_1_, employee0_.salary as salary3_1_, employee0_.dept_id_fk as dept4_1_ from hibernate.employee employee0_ 12
二、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>
1.<diskStore>: 指定一個目錄:當 EHCache 把數據寫到硬盤上時, 將把數據寫到這個目錄下.
2.<defaultCache>: 設置緩存的默認數據過期策略
3.<cache> 設定具體的命名緩存的數據過期策略。每個命名緩存代表一個緩存區域
4.緩存區域(region):一個具有名稱的緩存塊,可以給每一個緩存塊設置不同的緩存策略。如果沒有設置任何的緩存區域,則所有被緩存的對象,都將使用默認的緩存策略。即:<defaultCache.../>
5.Hibernate在不同的緩存區域保存不同的類/集合。
6.cache 元素的屬性
(1)name:設置緩存的名字,它的取值為類的全限定名或類的集合的名字
(2)maxInMemory:設置基於內存的緩存中可存放的對象最大數目
(3)eternal:設置對象是否為永久的,true表示永不過期,此時將忽略timeToIdleSeconds 和 timeToLiveSeconds屬性; 默認值是false。
(4)timeToIdleSeconds:設置對象空閒最長時間,以秒為單位, 超過這個時間,對象過期。當對象過期時,EHCache會把它從緩存中清除。如果此值為0,表示對象可以無限期地處於空閒狀態。
(5)timeToLiveSeconds:設置對象生存最長時間,超過這個時間,對象過期。如果此值為0,表示對象可以無限期地存在於緩存中. 該屬性值必須大於或等於 timeToIdleSeconds 屬性值。
(6)overflowToDisk:設置基於內存的緩存中的對象數目達到上限後,是否把溢出的對象寫到基於硬盤的緩存中。
三、管理 Session
–Session 對象的生命周期與本地線程綁定
–Session 對象的生命周期與 JTA 事務綁定
–Hibernate 委托程序管理 Session 對象的生命周期
四、批量處理數據
建議通過 JDBC 的方式來進行批量操作。
@Test public void testBathch() { session.doWork(new Work() { @Override public void execute(Connection connection) throws SQLException { // 執行批量操作 } }); }
五、總結
配置 Hibernate 二級緩存的步驟:
1.配置 Hibernate 配置文件
(1)配置啟用二級緩存
<property name="cache.use_second_level_cache">true</property>
(2)配置二級緩存使用的產品
<property name="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.EhCacheRegionFactory</property>
(3)配置對哪些類使用 hibernate 的二級緩存以及並發策略
<class-cache class="com.solverpeng.hql.Department" usage="read-write"/> <collection-cache collection="com.solverpeng.hql.Department.emps" usage="read-write"/> <class-cache class="com.solverpeng.hql.Employee" usage="read-write"/>
(4)在 hbm 文件中配置緩存
<set name="emps" inverse="true"> <cache usage="read-write"/> <key> <column name="dept_id_fk" not-null="true"/> </key> <one-to-many not-found="ignore" class="com.solverpeng.hql.Employee"/> </set>
2. 對於集合緩存來說,還需要配置集合中的元素對應的持久化類也使用二級緩存! 否則將會多出 n 條 SQL 語句.
3. 查詢緩存
(1)在 Hibernate 配置文件中開啟查詢緩存支持:<property name="cache.use_query_cache">true</property>
(2)調用 Query 或 Criteria 的 setCacheable(true)方法
(3)查詢緩存依賴於二級緩存