Hibernate Session (會(huì)話對(duì)象)
1. 前言
本節(jié)課程將和大家一起聊聊 Hibernate 的核心組件之一: Session 對(duì)象。
通過本節(jié)課程,你將了解到:
- 創(chuàng)建 Session 對(duì)象的 2 個(gè)方法;
- 線程上下文的作用。
2. 創(chuàng)建 Session 對(duì)象
Session 是 Hibernate 的重要組件之一,是交給開發(fā)者的一把利劍。開發(fā)者可使用 Session 對(duì)象提供的增、刪、改、查(Crud)等方法實(shí)現(xiàn)基礎(chǔ)的數(shù)據(jù)操作。
SessionFactory 提供了 2 個(gè)方法用來創(chuàng)建 Session :
- openSession() 方法;
Session session = sessionFactory.openSession();
- getCurrentSession() 方法。
Session session = sessionFactory.getCurrentSession();
這 2 個(gè)方法創(chuàng)建的 Session 對(duì)象有區(qū)別嗎?
肯定有!是什么?讓代碼來回答!
2.1 使用 openSession() 方法
運(yùn)行下面測(cè)試實(shí)例:
Configuration configuration = new Configuration().configure();
// 服務(wù)注冊(cè)
ServiceRegistry serviceRegistry = new ServiceRegistryBuilder().applySettings(configuration.getProperties())
.buildServiceRegistry();
// 會(huì)話工廠
SessionFactory sessionFactory = configuration.buildSessionFactory(serviceRegistry);
// 會(huì)話對(duì)象
Session session = sessionFactory.openSession();
Session session1 = sessionFactory.openSession();
System.out.println(session==session1);
System.out.println(session.hashCode());
System.out.println(session1.hashCode());
// 省略其它代碼……
上面代碼沒有具體的數(shù)據(jù)庫(kù)請(qǐng)求操作。僅僅使用 openSession() 方法創(chuàng)建了 2 個(gè) Session 對(duì)象,并判斷這 2 個(gè)對(duì)象是不是同一個(gè)對(duì)象:
System.out.println(session==session1);
System.out.println(session.hashCode());
System.out.println(session1.hashCode());
運(yùn)行后控制臺(tái)信息:
false
782505238
977552154注:每次運(yùn)行測(cè)試實(shí)例,輸出的哈希值會(huì)不同!
輸出結(jié)果可知,兩個(gè) Session 對(duì)象:
- 有自己的 hashCode ;
- 內(nèi)存地址不相同。
結(jié)論很重要:
每調(diào)用一次 openSession() 則創(chuàng)建了一個(gè)具有獨(dú)立內(nèi)存地址的 Session 對(duì)象。
2.2 使用 getCurrentSession() 方法
把上面測(cè)試實(shí)例中的 openSession() 方法轉(zhuǎn)換成 getCurrentSession() 方法:
Session session = sessionFactory.getCurrentSession();
好像出現(xiàn)了一個(gè)很大的坑,測(cè)試結(jié)果喜洋洋:
恭喜!出現(xiàn)紅彤彤的異常 “慶?!?。
SessionFactory 提供了 getCurrentSession() 方法,不用來創(chuàng)建 Session 對(duì)象,反倒用異常傷害使用者的積極性和對(duì) Hibernate 的情感。其意義何在呀?
存在必有存在的理因,與其自怨自艾,不如找到原因。分析異常:
No CurrentSessionContext configured!
沒有配置當(dāng)前會(huì)話對(duì)象的上下文!啥意思?暈……
開始逆推:
推斷下可知使用 getCurrentSession() 方法前,需要在主配置文件中配置某一項(xiàng)信息,現(xiàn)在因?yàn)闆]配置所以出錯(cuò)。
事不宜遲!馬上行動(dòng),翻閱官方提供的文檔,查找主配置文件中所有 可配置項(xiàng)(趁機(jī)會(huì)復(fù)習(xí)配置內(nèi)容),找到一個(gè)比較相近的配置屬性:
current_session_context_class
其配置值可以是 jta、thread、managed、custom.Class。如果希望在線程生命周期內(nèi)使用 Session 對(duì)象,則選擇 thread。
<property name="current_session_context_class">thread</property>
再測(cè)試下面的實(shí)例:
Session session = sessionFactory.getCurrentSession();
System.out.println(session);
控制臺(tái)輸入 Session 對(duì)象創(chuàng)建成功啦。至此,得到一個(gè)結(jié)論:
使用 getCurrentSession() 之前需要先配置 current_session_context_class 屬性。
2.3 更多細(xì)節(jié)繼續(xù)展開
執(zhí)行下面實(shí)例:
Session session = sessionFactory.getCurrentSession();
Session session1 = sessionFactory.getCurrentSession();
System.out.println(session==session1);
System.out.println(session.hashCode());
System.out.println(session1.hashCode());
控制臺(tái)輸出:
true
194707680
194707680
哈希值一樣,內(nèi)存地址一樣。結(jié)論是:兩次方法調(diào)用居然只創(chuàng)建了一個(gè)對(duì)象。
幾番折騰下來,到了總結(jié)的時(shí)候:
SessionFactory 使用 openSession() 方法創(chuàng)建會(huì)話對(duì)象,因?yàn)?Session 是線程不安全的,建議用完后就關(guān)閉。需要時(shí)再創(chuàng)建??!
需求總是多樣化的:
在某種特定的上下文環(huán)境中,如在一個(gè)線程生命周期內(nèi),希望能重用 Session 對(duì)象,又該如何實(shí)現(xiàn)?
getCurrentSession() 方法執(zhí)行流程:
- 調(diào)用此方法之前先檢查線程上下文(也可以是其它上下文)中是否存在 Session 對(duì)象;
- 如果存在,直接從線程上下文中獲取;
- 如果沒有,調(diào)用 openSession() 方法創(chuàng)建會(huì)話對(duì)象,然后保存在線程上下文中。具體如何保存,稍后再說。
本質(zhì)上 getCurrentSession() 方法是對(duì) openSession() 方法的高級(jí)應(yīng)用封裝。
現(xiàn)在明白 openSession()、getCurrentSession() 這 2 個(gè)方法創(chuàng)建 Session 對(duì)象的區(qū)別了吧。
使用中有一點(diǎn)需要引起注意:
getCurrentSession() 方法創(chuàng)建的 Session 其生命周期附注于指定的上下文對(duì)象中,不要在代碼中顯示關(guān)閉:
session.close();
否則會(huì)拋出如下異常:
好學(xué)如你一定會(huì)問,創(chuàng)建的 Session 又是如何附注于指定的線程生命周期中的。
3. 線程上下文
current_session_context_class 可配置值除 thread 外還有 jta、managed 等,簡(jiǎn)單描述下:
- 當(dāng)使用本地 Jdbc 事務(wù)時(shí)選擇 Thread。
- 當(dāng)使用全局 jta 事務(wù)時(shí)選擇 jta。
- 當(dāng)使用 session 管理機(jī)制時(shí)選擇 managed;
如和 Spring 一起整合使用時(shí),使用 Spring 的事務(wù)管理機(jī)制。
主要聊聊 thread 上下文是如何實(shí)現(xiàn)保存 Session,回顧一下上一節(jié)課程 HibernateSessionFactory 類中的代碼片段:
private static final ThreadLocal<Session> threadLocal = new ThreadLocal<Session>();
public static Session getSession() throws HibernateException {
Session session = (Session)threadLocal.get();
aif(session == null || !session.isOpen()) {
session = (sessionFactory!= null) ? sessionFactory.openSession():null;
threadLocal.set(session);
}
return session;
}
實(shí)現(xiàn)的關(guān)鍵就在于 ThreadLocal 這個(gè)類,ThreadLocal 是 Java SE 原生 API,此類實(shí)例化對(duì)象本質(zhì)就是一個(gè) Map 集合,與 Map 保存數(shù)據(jù)時(shí)不同,key 由線程對(duì)象充當(dāng)。
使用此對(duì)象可以為每一個(gè)線程保存只屬于當(dāng)前線程的數(shù)據(jù)。
HibernateSessionFactory 中重構(gòu)過的 getSession() 方法解析如下:
- 以當(dāng)前線程對(duì)象為 key 查詢 threadLocal 集合中是否存在 Session 對(duì)象,如有直接返回;
Session session = (Session) threadLocal.get();
return session;
- 如果沒有,則創(chuàng)建 Session 對(duì)象,用當(dāng)前線程作為 key 保存 Session 對(duì)象到 threadLocal 對(duì)象中。
if(session == null || !session.isOpen()) {
session = (sessionFactory!= null) ? sessionFactory.openSession():null;
threadLocal.set(session);
}
如上面代碼所述,只要線程生命周期沒走到盡頭,與其關(guān)聯(lián)的 Session 對(duì)象就能重復(fù)使用。
并且每一個(gè)線程中使用的是與本線程相關(guān)聯(lián)的 Session,避免了多線程環(huán)境下 Session 變成臨界資源,避開線程安全隱患。
4. 小結(jié)
本節(jié)課程聊到 Hibernate 為開發(fā)者提供的核心組件 Session。希望通過本課程內(nèi)容讓開發(fā)者對(duì)它有更深入的了解,在使用過程避開一些常見錯(cuò)誤。
SessionFactory 提供了 2 個(gè)方法用來創(chuàng)建 Session ,各自有使用的場(chǎng)景。
結(jié)束 Session 之前,總結(jié)一下它的幾個(gè)特性:
- Session 不是線程安全的,它代表與數(shù)據(jù)庫(kù)之間的一次操作,是一個(gè)介于 Connection 和 Transaction 之間的概念;
- Session 也稱為持久化管理器,因提供了相關(guān)的持久化方法;
- Session 通過 SessionFactory 來創(chuàng)建,使用完畢后,需要調(diào)用 close() 方法顯示關(guān)閉;
- 建議 Session 作用域一般指定為方法級(jí)別。