亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

Hibernate Session (會話對象)

1. 前言

本節課程將和大家一起聊聊 Hibernate 的核心組件之一: Session 對象。

通過本節課程,你將了解到:

  • 創建 Session 對象的 2 個方法;
  • 線程上下文的作用。

2. 創建 Session 對象

SessionHibernate 的重要組件之一,是交給開發者的一把利劍。開發者可使用 Session 對象提供的增、刪、改、查(Crud)等方法實現基礎的數據操作。

SessionFactory 提供了 2 個方法用來創建 Session

  • openSession() 方法;
 Session session = sessionFactory.openSession();
  • getCurrentSession() 方法。
Session session = sessionFactory.getCurrentSession();

2 個方法創建的 Session 對象有區別嗎?

肯定有!是什么?讓代碼來回答!

2.1 使用 openSession() 方法

運行下面測試實例:

Configuration configuration = new Configuration().configure();
// 服務注冊
ServiceRegistry serviceRegistry = new ServiceRegistryBuilder().applySettings(configuration.getProperties())
.buildServiceRegistry();
// 會話工廠
SessionFactory sessionFactory = configuration.buildSessionFactory(serviceRegistry);
// 會話對象
Session session = sessionFactory.openSession();
Session session1 = sessionFactory.openSession();
System.out.println(session==session1);
System.out.println(session.hashCode());
System.out.println(session1.hashCode());
// 省略其它代碼……

上面代碼沒有具體的數據庫請求操作。僅僅使用 openSession() 方法創建了 2Session 對象,并判斷這 2 個對象是不是同一個對象:

System.out.println(session==session1);
System.out.println(session.hashCode());
System.out.println(session1.hashCode());

運行后控制臺信息:

false
782505238
977552154

注:每次運行測試實例,輸出的哈希值會不同!

輸出結果可知,兩個 Session 對象:

  • 有自己的 hashCode ;
  • 內存地址不相同。

結論很重要:

每調用一次 openSession() 則創建了一個具有獨立內存地址的 Session 對象。

2.2 使用 getCurrentSession() 方法

把上面測試實例中的 openSession() 方法轉換成 getCurrentSession() 方法:

Session session = sessionFactory.getCurrentSession();

好像出現了一個很大的坑,測試結果喜洋洋:

圖片描述

恭喜!出現紅彤彤的異常 “慶?!薄?/p>

SessionFactory 提供了 getCurrentSession() 方法,不用來創建 Session 對象,反倒用異常傷害使用者的積極性和對 Hibernate 的情感。其意義何在呀?

存在必有存在的理因,與其自怨自艾,不如找到原因。分析異常:

No CurrentSessionContext configured!

沒有配置當前會話對象的上下文!啥意思?暈……

開始逆推:

推斷下可知使用 getCurrentSession() 方法前,需要在主配置文件中配置某一項信息,現在因為沒配置所以出錯。

事不宜遲!馬上行動,翻閱官方提供的文檔,查找主配置文件中所有 可配置項(趁機會復習配置內容),找到一個比較相近的配置屬性:

current_session_context_class

其配置值可以是 jta、thread、managed、custom.Class。如果希望在線程生命周期內使用 Session 對象,則選擇 thread。

<property name="current_session_context_class">thread</property>

再測試下面的實例:

Session session = sessionFactory.getCurrentSession();
System.out.println(session);

控制臺輸入 Session 對象創建成功啦。至此,得到一個結論:

使用 getCurrentSession() 之前需要先配置 current_session_context_class 屬性。

2.3 更多細節繼續展開

執行下面實例:

Session session = sessionFactory.getCurrentSession();
Session session1 = sessionFactory.getCurrentSession();
System.out.println(session==session1);
System.out.println(session.hashCode());
System.out.println(session1.hashCode());

控制臺輸出:

true
194707680
194707680

哈希值一樣,內存地址一樣。結論是:兩次方法調用居然只創建了一個對象。

幾番折騰下來,到了總結的時候:

SessionFactory 使用 openSession() 方法創建會話對象,因為 Session 是線程不安全的,建議用完后就關閉。需要時再創建!!

需求總是多樣化的:

在某種特定的上下文環境中,如在一個線程生命周期內,希望能重用 Session 對象,又該如何實現?

getCurrentSession() 方法執行流程:

  • 調用此方法之前先檢查線程上下文(也可以是其它上下文)中是否存在 Session 對象;
  • 如果存在,直接從線程上下文中獲?。?/li>
  • 如果沒有,調用 openSession() 方法創建會話對象,然后保存在線程上下文中。具體如何保存,稍后再說。

本質上 getCurrentSession() 方法是對 openSession() 方法的高級應用封裝。

現在明白 openSession()、getCurrentSession()2 個方法創建 Session 對象的區別了吧。

使用中有一點需要引起注意:

getCurrentSession() 方法創建的 Session 其生命周期附注于指定的上下文對象中,不要在代碼中顯示關閉:

session.close();

否則會拋出如下異常:

圖片描述

好學如你一定會問,創建的 Session 又是如何附注于指定的線程生命周期中的。

3. 線程上下文

current_session_context_class 可配置值除 thread 外還有 jtamanaged 等,簡單描述下:

  • 當使用本地 Jdbc 事務時選擇 Thread
  • 當使用全局 jta 事務時選擇 jta。
  • 當使用 session 管理機制時選擇 managed

如和 Spring 一起整合使用時,使用 Spring 的事務管理機制。

主要聊聊 thread 上下文是如何實現保存 Session,回顧一下上一節課程 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;
}

實現的關鍵就在于 ThreadLocal 這個類,ThreadLocal 是 Java SE 原生 API,此類實例化對象本質就是一個 Map 集合,與 Map 保存數據時不同,key 由線程對象充當。

使用此對象可以為每一個線程保存只屬于當前線程的數據。

HibernateSessionFactory 中重構過的 getSession() 方法解析如下:

  1. 當前線程對象為 key 查詢 threadLocal 集合中是否存在 Session 對象,如有直接返回;
Session session = (Session) threadLocal.get();
return session;
  1. 如果沒有,則創建 Session 對象,用當前線程作為 key 保存 Session 對象到 threadLocal 對象中。
if(session == null || !session.isOpen()) {
    session = (sessionFactory!= null) ? sessionFactory.openSession():null;
    threadLocal.set(session);
}

如上面代碼所述,只要線程生命周期沒走到盡頭,與其關聯的 Session 對象就能重復使用。
并且每一個線程中使用的是與本線程相關聯的 Session,避免了多線程環境下 Session 變成臨界資源,避開線程安全隱患。

4. 小結

本節課程聊到 Hibernate 為開發者提供的核心組件 Session。希望通過本課程內容讓開發者對它有更深入的了解,在使用過程避開一些常見錯誤。

SessionFactory 提供了 2 個方法用來創建 Session ,各自有使用的場景。

結束 Session 之前,總結一下它的幾個特性:

  • Session 不是線程安全的,它代表與數據庫之間的一次操作,是一個介于 ConnectionTransaction 之間的概念;
  • Session 也稱為持久化管理器,因提供了相關的持久化方法;
  • Session 通過 SessionFactory 來創建,使用完畢后,需要調用 close() 方法顯示關閉;
  • 建議 Session 作用域一般指定為方法級別。