Hibernate ThreadLocal管理Session


在利用Hibernate开发DAO模块时,我们和Session打的交道最多,所以如何合理的管理Session,避免Session的频繁创建和销毁,对于提高系统的性能来说是非常重要的,可以通过eclipse的插件来自动生成这些代码的,当然效果是不错的,但是总是觉得不爽(没有读懂那些冗长的代码),所以我们可以自己实现Session管理的代码。我们知道Session是由SessionFactory负责创建的,而SessionFactory的实现是线程安全的,多个并发的线程可以同时访问一个SessionFactory并从中获取Session实例,那么Session是否是线程安全的呢?很遗憾,答案是否定的。Session中包含了数据库操作相关的状态信息,那么说如果多个线程同时使用一个Session实例进行CRUD,就很有可能导致数据存取的混乱,你能够想像那些你根本不能预测执行顺序的线程对你的一条记录进行操作的情形吗?


在Session的众多管理方案中,我们今天来认识一种名为ThreadLocal模式的解决方案。


早在Java1.2推出之时,Java平台中就引入了一个新的支持:java.lang.ThreadLocal,给我们在编写多线程程序时提供了一种新的选择。ThreadLocal是什么呢?其实ThreadLocal并非是一个线程的本地实现版本,它并不是一个Thread,而是thread local variable(线程局部变量)。也许把它命名为ThreadLocalVar更加合适。线程局部变量(ThreadLocal)其实的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。从线程的角度看,就好像每一个线程都完全拥有一个该变量。


ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单,在ThreadLocal类中有一个Map,用于存储每一个线程的变量的副本。比如下面的示例实现(为了简单,没有考虑集合的泛型):

public class ThreadLocal {   
     private Map values = Collections.synchronizedMap(new HashMap());   
     public Object get() {   
        Thread currentThread = Thread.currentThread();    
        Object result = values.get(currentThread);    
        if(result == null&&!values.containsKey(currentThread)) {   
           result = initialValue();   
           values.put(currentThread, result);    
        }   
        return result;    
     }   
     public void set(Object newValue) {   
        values.put(Thread.currentThread(), newValue);   
     }   
     public Object initialValue() {   
        return null;    
     }   
}   


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

public class HibernateUtil {   
       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);   
       }   
}  


只要借助上面的工具类获取Session实例,我们就可以实现线程范围内的Session共享,从而避免了线程中频繁的创建和销毁Session实例。当然,不要忘记在用完后关闭Session。

 

另外还有一种方式配置文件的方式也可以实现同样的效果,底层Hibernate帮我们实现了ThreadLocal管理Sessionn的行为。

具体做法如下:

1)在hibernate.cfg.xml配置:

<!-- 让session被TheadLocal管理 -->
<property name="hibernate.current_session_context_class">thread</property>

2)在HibernateUtil工具类获取Session

/**
	 * 让外部获取Session对象
	 */
	public static Session getSession(){
		//return factory.openSession();
		return factory.getCurrentSession();
	}

改为使用getCurrentSession()方法获取Session,这样获取的Session对象就已经ThreadLocal所管理。