Hibernate3回顾-3-Session管理

时间:2021-06-17 19:31:14

3.Session管理

仅为个人理解.请指正

3.1背景

由于Configuration的创建耗费系统的资源。所以有必要只将Configuration实例化一次,之后通过SessionFactory获取session会话。一般都会手动封装一个HibernateUtil类(未使用Spring管理的前提下). 该类的作用使Hibernate加载配置文件config, 创建sessionFactory等只运行一次. 如下代码:

 public class HibernateUtil {
private static final SessionFactory sessionFactory = buildSessionFactory();
private static SessionFactory buildSessionFactory() {
try {
// Create the SessionFactory from hibernate.cfg.xml
return new Configuration().configure().buildSessionFactory();
} catch (Throwable ex) {
// Make sure you log the exception, as it might be swallowed
System.err.println("Initial SessionFactory creation failed." + ex);
throw new ExceptionInInitializerError(ex);
}
}
public static SessionFactory getSessionFactory() {
return sessionFactory;
}
}

在静态方法中实例化,获取sessionFactory的实例。然后通过getSessionFactory().getOpenSession();

获取session。

类似的,之前有一种相对原始简单的进一步封装方式。如下:可见这种简单的封装进一步把增删改查等需求打包。

 package com.test.util;

 import java.io.Serializable;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration; /**
* 工具类网络优化版
*/
public final class HibernateUtil2 { //final类不允许被继承 {
private static SessionFactory sessionFactory; //私有静态属性
private static ThreadLocal session = new ThreadLocal(); private HibernateUtil2() { //私有的构造方法 禁止实例化
} static { //static块只会在虚拟机进行加载时被执行一次
Configuration cfg = new Configuration();
cfg.configure(); //读取配置文件信息,默认配置文件是"hibernate.cfg.xml"
sessionFactory = cfg.buildSessionFactory();
} public static Session getThreadLocalSession() {
Session s = (Session) session.get();
if (s == null) {
s = getSession();
session.set(s);
}
return s;
} public static void closeSession() {
Session s = (Session) session.get();
if (s != null) {
s.close();
session.set(null);
}
} public static SessionFactory getSessionFactory() {
return sessionFactory;
} public static Session getSession() {
return sessionFactory.openSession();
}
//数据库操作:增
public static void add(Object entity) {
Session s = null;
Transaction tx = null;
try {
s = HibernateUtil2.getSession();
tx = s.beginTransaction();
s.save(entity);
tx.commit();
} finally { //确保Session被关闭
if (s != null)
s.close();
}
}
//数据库操作:改
public static void update(Object entity) {
Session s = null;
Transaction tx = null;
try {
s = HibernateUtil2.getSession();
tx = s.beginTransaction();
s.update(entity);
tx.commit();
} finally {
if (s != null)
s.close();
}
}
//数据库操作:删
public static void delete(Object entity) {
Session s = null;
Transaction tx = null;
try {
s = HibernateUtil2.getSession();
tx = s.beginTransaction();
s.delete(entity);
tx.commit();
} finally {
if (s != null)
s.close();
}
}
//数据库操作:查询 通过主键id查询数据库中一个表的一行信息。Class 对应数据库表 或者 domain中的实体对象类,Serializable 序列号
public static Object get(Class clazz, Serializable id) {
Session s = null;
try {
s = HibernateUtil2.getSession();
Object obj = s.get(clazz, id);
return obj;
} finally {
if (s != null)
s.close();
}
}
}

3.2 使用ThreadLocal管理session

总而言之:在HibernateUtil类中封装hibernate的管理.通过openSession取得 session,并将其放入ThreadLocal变量中. 这样业务逻辑中仅需通过工具类取得当前线程对应的session.使用完毕后,将 session关闭,将当前线程的ThreadLocal变量置为NULL. 保证线程归还线程池复用后,ThreadLocal为空,以免出现导致其他线程访问到本线程变量.

详细:

Session是由SessionFactory负责创建的,而SessionFactory的实现是线程安全的,多个并发的线程可以同时访问一个SessionFactory并从中获取Session实例,那么Session是否是线程安全的呢?很遗憾,答案是否定的。Session中包含了数据库操作相关的状态信息,那么说如果多个线程同时使用一个Session实例进行CRUD,就很有可能导致数据存取的混乱,你能够想像那些你根本不能预测执行顺序的线程对你的一条记录进行操作的情形吗?       在Session的众多管理方案中,我们今天来认识一种名为ThreadLocal模式的解决方案。ThreadLocal并非是一个线程的本地实现版本,它并不是一个Thread,而是thread local variable(线程局部变量)也许把它命名为ThreadLocalVar更加合适。线程局部变量(ThreadLocal)其实的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。从线程的角度看,就好像每一个线程都完全拥有一个该变量。

那麽具体如何利用ThreadLocal来管理Session呢?Hibernate官方文档手册的示例之中,提供了一个通过ThreadLocal维护Session的好榜样:

 package com.test.util;

 import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration; public class HibernateUtil_Thread {
public static final SessionFactory sessionFactory;
static {
try {
sessionFactory = new Configuration().configure().buildSessionFactory();
} catch (Throwable ex) {
throw new ExceptionInInitializerError(ex);
}
}
public static final ThreadLocal<Session> session = new ThreadLocal<Session>(); public static Session currentSession() throws HibernateException {
Session s = session.get();
if (s == null) {
s = sessionFactory.openSession();
session.set(s);
}
return s;
} public static void closeSession() throws HibernateException {
Session s = session.get();
if (s != null) {
s.close();
}
session.set(null);
}
}

使用eclipse工具也会生成类似上面的代码。如下:

 package com.test.db;

 import org.hibernate.HibernateException;
import org.hibernate.Session;
import org.hibernate.cfg.Configuration;
import org.hibernate.cfg.AnnotationConfiguration;
/**
* Configures and provides access to Hibernate sessions, tied to the
* current thread of execution. Follows the Thread Local Session
* pattern, see {@link http://hibernate.org/42.html }.
*/
public class HibernateSessionFactory { /**
* Location of hibernate.cfg.xml file.
* Location should be on the classpath as Hibernate uses
* #resourceAsStream style lookup for its configuration file.
* The default classpath location of the hibernate config file is
* in the default package. Use #setConfigFile() to update
* the location of the configuration file for the current session.
*/
private static String CONFIG_FILE_LOCATION = "/hibernate.cfg.xml";
private static final ThreadLocal<Session> threadLocal = new ThreadLocal<Session>();
private static Configuration configuration = new AnnotationConfiguration();
private static org.hibernate.SessionFactory sessionFactory;
private static String configFile = CONFIG_FILE_LOCATION; static {
try {
configuration.configure(configFile);
sessionFactory = configuration.buildSessionFactory();
} catch (Exception e) {
System.err
.println("%%%% Error Creating SessionFactory %%%%");
e.printStackTrace();
}
}
private HibernateSessionFactory() {
} /**
* Returns the ThreadLocal Session instance. Lazy initialize
* the <code>SessionFactory</code> if needed.
*
* @return Session
* @throws HibernateException
*/
public static Session getSession() throws HibernateException {
Session session = (Session) threadLocal.get(); if (session == null || !session.isOpen()) {
if (sessionFactory == null) {
rebuildSessionFactory();
}
session = (sessionFactory != null) ? sessionFactory.openSession()
: null;
threadLocal.set(session);
} return session;
} /**
* Rebuild hibernate session factory
*
*/
public static void rebuildSessionFactory() {
try {
configuration.configure(configFile);
sessionFactory = configuration.buildSessionFactory();
} catch (Exception e) {
System.err
.println("%%%% Error Creating SessionFactory %%%%");
e.printStackTrace();
}
} /**
* Close the single hibernate session instance.
*
* @throws HibernateException
*/
public static void closeSession() throws HibernateException {
Session session = (Session) threadLocal.get();
threadLocal.set(null); if (session != null) {
session.close();
}
} /**
* return session factory
*
*/
public static org.hibernate.SessionFactory getSessionFactory() {
return sessionFactory;
} /**
* return session factory
*
* session factory will be rebuilded in the next call
*/
public static void setConfigFile(String configFile) {
HibernateSessionFactory.configFile = configFile;
sessionFactory = null;
} /**
* return hibernate configuration
*
*/
public static Configuration getConfiguration() {
return configuration;
}
}

3.3 新的解决方案:getCurrentSession

总而言之:Hibernate的SessionFactory提供获取session的新方法getCurrentSession (获得与当前线程绑定的session). 内部通过代理封装,此方式得到的session 不仅和当前线程绑定,也无需手动开关. 默认在事务提交之后,session自动关闭. 此外hibernate.cfg.xml配置文件中也需配置

<property name="current_session_context_class">thread</property> 基于线程 。
 
题外话引入Spring之后.sessionfactory的创建等都交给spring管理.Spring也提供了HibernateTemplate,HibernateDaoSupport这样的封装方法. 用户可以不再考虑session的管理,事务的开启关闭.只需配置事务即可. 而所谓session关闭后,因延迟加载导致前台无法显示的问题以往解决方式为强制全部加载,现在也可通过在web.xml中配置
org.springframework.orm.hibernate3.support.OpenSessionInViewFilter来解决.
 
详细描述

使用 Hibernate 的大多数应用程序需要某种形式的“上下文相关的”会话,特定的会话在整个特定的上下文范围内始终有效。然后不同应用程序中对“上下文”一词的定义和理解是不同的。

在Hibernate3.0之前,使用Hibernate的程序要么自行编写基于ThreadLocal的上下文会话,要么采用HibernateUtil这样的辅助类,要么使用第三方框架(如spring或pico),他们提供了基于代理或者基于拦截器的上下文相关会话。

Hibernate 3.1之后,Hibernate增加了SessionFactory.getCurrentSession()方法。该方法实现是可拔插的(通过org.hibernate.context.CurrentSessionContext)和新的配置参数

(hibernate.current_session_context_class),以便对什么是当前会话的范围(scope)和上下文(context)的定义进行拔插。

请参阅 org.hibernate.context.CurrentSessionContext接口的 Javadoc,那里有关于它的契约的详

细讨论。它定义了单一的方法,currentSession(),特定的实现用它来负责跟踪当前的上下文相关

的会话。Hibernate 内置了此接口的三种实现:

org.hibernate.context.JTASessionContext当前会话根据  JTA 来跟踪和界定。这和以前的仅支

JTA 的方法是完全一样的。详情请参阅 Javadoc

org.hibernate.context.ThreadLocalSessionContext当前会话通过当前执行的线程来跟踪和界

。详情也请参阅 Javadoc

org.hibernate.context.ManagedSessionContext:当前会话通过当前执行的线程来跟踪和界定。但

是,你需要负责使用这个类的静态方法将  Session 实例绑定、或者取消绑定,它并不会打开

open)、flush 或者关闭(close)任何 Session

这两种实现都提供了“每数据库事务对应一个session”的编程模型,也称作每次请求对应一个session。Hibernate session的起始和终结由数据库事务的生存来控制。假若你采用自行编写代码来管理事务(比如,在纯粹的J2SE,或者JTA/UserTransaction/BMT),建议你使用Hibernate Transaction API来把底层事务实现从你的代码中隐藏掉。如果你在支持CMT的EJB容器中执行,事务边界是声明式定义的,你不需要在代码中进行任何事务或session管理操作。请参阅第 11 章 事务和并发一节来阅读更多的内容和示例代码。

hibernate.current_session_context_class 配置参数定义了应该采用哪个org.hibernate.context.CurrentSessionContext实现。注意,为了向下兼容,如果未配置此参数,但是存在org.hibernate.transaction.TransactionManagerLookup的配置,Hibernate会采用org.hibernate.context.JTASessionContext。一般而言,此参数的值指明了要使用的实现类的全名,但那两个内置的实现可以使用简写,"jta""thread"。      

 

常见混淆

1 getCurrentSession创建的session会和绑定到当前线程,而openSession不会。

2 getCurrentSession创建的线程会在事务回滚或事物提交后自动关闭,而openSession必须手动关闭

这里getCurrentSession本地事务(本地事务:jdbc)时 要在配置文件里进行如下设置

* 如果使用的是本地事务(jdbc事务)  <property name="hibernate.current_session_context_class">thread</property>  * 如果使用的是全局事务(jta事务)  <property name="hibernate.current_session_context_class">jta</property>

getCurrentSession () 使用当前的session openSession()         重新建立一个新的session

在一个应用程序中,如果DAO 层使用Spring 的hibernate 模板,通过Spring 来控制session 的生命周期,则首选getCurrentSession ()。