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();

好像出现了一个很大的坑,测试结果喜洋洋:

图片描述

恭喜!出现红彤彤的异常 “庆祝”。

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 对象;
  • 如果存在,直接从线程上下文中获取;
  • 如果没有,调用 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 作用域一般指定为方法级别。