Hibernate【快取】知識要點
物件狀態
Hibernate中物件的狀態:
- 臨時/瞬時狀態
- 持久化狀態
- 遊離狀態
學習Hibernate的物件狀態是為了更清晰地知道Hibernate的設計思想,以及是一級快取的基礎…當然啦,也就一點點知識
臨時/瞬時狀態
當我們直接new出來的物件就是臨時/瞬時狀態的..
- 該物件還沒有被持久化【沒有儲存在資料庫中】
- 不受Session的管理
這裡寫圖片描述
持久化狀態
當儲存在資料庫中的物件就是持久化狀態了
- 當呼叫session的save/saveOrUpdate/get/load/list等方法的時候,物件就是持久化狀態
- 在資料庫有對應的資料
- 受Session的管理
- 當對物件屬性進行更改的時候,會反映到資料庫中!
這裡寫圖片描述
我們來測試一下:當對物件屬性進行更改的時候,會反映到資料庫中!
session.save(idCard);
idCard.setIdCardName("我是測試持久化物件");
這裡寫圖片描述
遊離狀態
當Session關閉了以後,持久化的物件就變成了遊離狀態了…
- 不處於session的管理
- 資料庫中有對應的記錄
這裡寫圖片描述
一級快取
Hibernate有一級快取和二級快取之分,這裡主要講解一級快取
什麼是一級快取?
Hibenate中一級快取,也叫做session的快取,它可以在session範圍內減少資料庫的訪問次數! 只在session範圍有效! Session關閉,一級快取失效!
只要是持久化物件狀態的,都受Session管理,也就是說,都會在Session快取中!
Session的快取由hibernate維護,使用者不能操作快取內容; 如果想操作快取內容,必須通過hibernate提供的evit/clear方法操作。
為什麼要是使用快取?
減少對資料庫的訪問次數!從而提升hibernate的執行效率!
測試
我們來看一下Hibernate是怎麼減少對資料庫訪問的次數的。
現在我的User表有這麼一條記錄:
這裡寫圖片描述
//把資料放進cache User user = (User) session.get(User.class, 1); //發現要修改的欄位和cache一樣,不執行 user.setUserName("你好2");
這裡寫圖片描述
這裡寫圖片描述
取資料也是一樣的
User user = null;
user = (User) session.get(User.class, 1);
user = (User) session.get(User.class, 1);
這裡寫圖片描述
快取相關的方法
和快取有關常用的方法有三個:
- session.flush(); 讓一級快取與資料庫同步
- session.evict(arg0); 清空一級快取中指定的物件
- session.clear(); 清空一級快取中快取的所有物件
- clear
User user = null;
user = (User) session.get(User.class, 1);
//清除快取,那麼下面獲取的時候,就不能從快取裡面拿了
session.clear();
user = (User) session.get(User.class, 1);
- flush
在有快取的情況下,修改同一條記錄的資料,以最後的為準…因此只有一條update
User user = null;
user = (User) session.get(User.class, 1);
user.setUserName("我是第一");
user = (User) session.get(User.class, 1);
user.setUserName("我是第二");
這裡寫圖片描述
我讓強制讓它和資料庫同步的話,就有兩條update了
User user = null;
user = (User) session.get(User.class, 1);
user.setUserName("我是第一");
session.flush();
user = (User) session.get(User.class, 1);
user.setUserName("我是第二");
這裡寫圖片描述
一般地,我們在批處理的時候會用,因為快取也是有大小的,如果1000條資料插入進去都要快取,那麼Hibernate可能就崩了…
- 每隔一定記錄數,先與資料庫同步 flush()
- 再清空快取 clear()
值得注意的是:不同的Session是不會共享快取的!
Iterator與list
我們使用HQL查詢全部資料的時候,可以使用list()得到所有的資料,也可以使用iterator()得到一個迭代器,再遍歷迭代器…那它們有什麼區別呢?
。。。。當時看視訊的時候說是下圖:
這裡寫圖片描述
但是我在測試的時候:List也可以獲取快取的資料
這裡寫圖片描述
當然啦,Iterator也是可以獲取快取的資料
這裡寫圖片描述
因此,在獲取資料的時候還是使用list()方便!
懶載入
懶載入就是當使用資料的時候才去獲取資料、執行對應的SQL語句…當還沒用到資料的時候,就不載入對應的資料!
主要目的就是為了提高Hibernate的效能,提高執行效率!
- get: 及時載入,只要呼叫get方法立刻向資料庫查詢
- load:預設使用懶載入,當用到資料的時候才向資料庫查詢。
懶載入再次體驗
User user = (User) session.load(User.class, 1);
System.out.println("________");
System.out.println(user);
這裡寫圖片描述
我們可以在對應的配置檔案用通常lazy屬性來設定
關閉懶載入:
<class name="IdCard" table="IdCard" lazy="false">
這裡寫圖片描述
lazy有三個屬性:
- true 使用懶載入
- false 關閉懶載入
-
extra (在集合資料懶載入時候提升效率)【只有在set、list等集合標籤中使用】
- 在真正使用資料的時候才向資料庫傳送查詢的sql;
- 如果呼叫集合的size()/isEmpty()方法,只是統計,不真正查詢資料!
懶載入異常
當Session關閉後,就不能使用懶載入了,否則會報出異常
Exception in thread "main" org.hibernate.LazyInitializationException: could not initialize proxy - no Session
這裡寫圖片描述
報出了這個異常,我們有4種方法解決:
- 方式1: 先使用一下資料
- dept.getDeptName();
- 方式2:強迫代理物件初始化
- Hibernate.initialize(dept);
- 方式3:關閉懶載入
- 設定lazy=false;
- **方式4: 在使用資料之後,再關閉session! **
Hibernate二級快取
前面我們已經講解過了一級快取,一級快取也就是Session快取,只在Session的範圍內有效…作用時間就在Session的作用域中,範圍比較小
Hibernate為我們提供了二級快取功能:二級快取是基於應用程式的快取,所有的Session都可以使用
- Hibernate提供的二級快取有預設的實現,且是一種可插配的快取框架!如果使用者想用二級快取,只需要在hibernate.cfg.xml中配置即可; 不想用,直接移除,不影響程式碼。
- 如果使用者覺得hibernate提供的框架框架不好用,自己可以換其他的快取框架或自己實現快取框架都可以。
這裡寫圖片描述
Hibernate二級快取:儲存的是常用的類
配置二級快取
既然二級快取是Hibernate自帶的,那麼我們可以在hibernate.properties檔案中找到對應的資訊..
這裡寫圖片描述
- #hibernate.cache.use_second_level_cache false【二級快取預設不開啟,需要手動開啟】
- #hibernate.cache.use_query_cache true 【開啟查詢快取】
- ## choose a cache implementation 【二級快取框架的實現】
- #hibernate.cache.provider_class org.hibernate.cache.EhCacheProvider
- #hibernate.cache.provider_class org.hibernate.cache.EmptyCacheProvider
- hibernate.cache.provider_class org.hibernate.cache.HashtableCacheProvider 預設實現
- #hibernate.cache.provider_class org.hibernate.cache.TreeCacheProvider
- #hibernate.cache.provider_class org.hibernate.cache.OSCacheProvider
- #hibernate.cache.provider_class org.hibernate.cache.SwarmCacheProvider
通過配置檔案我們可以發現,二級快取預設是不開啟的,需要我們手動開啟,以下步驟:
- 1)開啟二級快取
- 2)指定快取框架
- 3)指定哪些類加入二級快取
開啟二級快取
在hibernate.cfg.xml檔案中開啟二級快取
<!-- a. 開啟二級快取 -->
<property name="hibernate.cache.use_second_level_cache">true</property>
指定快取框架
指定Hibernate自帶的二級快取框架就好了
<!-- b. 指定使用哪一個快取框架(預設提供的) -->
<property name="hibernate.cache.provider_class">org.hibernate.cache.HashtableCacheProvider</property>
指定哪些類加入二級快取
<!-- c. 指定哪一些類,需要加入二級快取 -->
<class-cache usage="read-write" class="zhongfucheng.aa.Monkey"/>
<class-cache usage="read-only" class="zhongfucheng.aa.Cat"/>
測試:
我們知道一級快取是Session的快取,那麼我們在測試二級快取的時候使用兩個Session來測試就好了。如果第二個Session拿到的是快取資料,那麼就證明二級快取是有用的。
package zhongfucheng.aa;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.hibernate.classic.Session;
public class App5 {
public static void main(String[] args) {
//獲取載入配置管理類
Configuration configuration = new Configuration();
//載入類對應的對映檔案!
configuration.configure().addClass(Animal.class);
//建立Session工廠物件
SessionFactory factory = configuration.buildSessionFactory();
//得到Session物件
Session session1 = factory.openSession();
//使用Hibernate操作資料庫,都要開啟事務,得到事務物件
Transaction transaction = session1.getTransaction();
//開啟事務
transaction.begin();
Monkey monkey = (Monkey) session1.get(Monkey.class,"40283f815be67f42015be67f43240001" );
System.out.println(monkey.getName());
System.out.println("-----------------------");
Session session2 = factory.openSession();
Transaction transaction2 = session2.getTransaction();
transaction2.begin();
Monkey monkey2 = (Monkey) session1.get(Monkey.class, "40283f815be67f42015be67f43240001");
System.out.println(monkey2.getName());
//提交事務
transaction.commit();
transaction2.commit();
//關閉Session
session1.close();
session2.close();
}
}
得到的是快取資料!
這裡寫圖片描述
快取策略
我們在把Animal類放進二級快取的時候,用法為只讀
這裡寫圖片描述
也就是說,只能讀取,不能寫入,我們來看看寫入會怎麼樣:
monkey2.setName("小猴子");
丟擲了異常….
這裡寫圖片描述
usage的屬性有4種:
- ** 放入二級快取的物件,只讀; **
- 非嚴格的讀寫
- 讀寫; 放入二級快取的物件可以讀、寫;
- (基於事務的策略)
集合快取
如果我們在資料庫查詢的資料是集合…Hibernate預設是沒有為集合資料設定二級快取的…因此還是需要去讀寫資料庫的資訊
接下來,我們就看看把集合設定為二級快取是什麼做的:
- 在hibernate.cgf.xml中配置物件中的集合為二級快取
<!-- 集合快取[集合快取的元素物件,也加加入二級快取] -->
<collection-cache usage="read-write" collection="cn.itcast.b_second_cache.Dept.emps"/>
- 測試程式碼:
public void testCache() {
Session session1 = sf.openSession();
session1.beginTransaction();
// a. 查詢一次
Dept dept = (Dept) session1.get(Dept.class, 10);
dept.getEmps().size();// 集合
session1.getTransaction().commit();
session1.close();
System.out.println("------");
// 第二個session
Session session2 = sf.openSession();
session2.beginTransaction();
// a. 查詢一次
dept = (Dept) session2.get(Dept.class, 10); // 二級快取配置好; 這裡不查詢資料庫
dept.getEmps().size();
session2.getTransaction().commit();
session2.close();
}
查詢快取
list()和iterator()會把資料放在一級快取,但一級快取只在Session的作用域中有效…如果想要跨Session來使用,就要設定查詢快取
我們在配置檔案中還看到了查詢快取這麼一條配置..
#hibernate.cache.use_query_cache true 【開啟查詢快取】
也就是說,預設的查詢資料是不放在二級快取中的,如果我們想要把查詢出來的資料放到二級快取,就需要在配置檔案中開啟
<!-- 開啟查詢快取 -->
<property name="hibernate.cache.use_query_cache">true</property>
- 在使用程式查詢的時候,也要呼叫setCacheable()方法,設定為查詢快取。
@Test
public void listCache() {
Session session1 = sf.openSession();
session1.beginTransaction();
// HQL查詢 【setCacheable 指定從二級快取找,或者是放入二級快取】
Query q = session1.createQuery("from Dept").setCacheable(true);
System.out.println(q.list());
session1.getTransaction().commit();
session1.close();
Session session2 = sf.openSession();
session2.beginTransaction();
q = session2.createQuery("from Dept").setCacheable(true);
System.out.println(q.list()); // 不查詢資料庫: 需要開啟查詢快取
session2.getTransaction().commit();
session2.close();
}