Hibernate中对Session管理
在各种Session 管理方案中, ThreadLocal 模式得到了大量使用。ThreadLocal 是Java中一种较为特殊的线程绑定机制。通过ThreadLocal存取的数据,总是与当前线程相关,也就是说,JVM 为每个运行的线程,绑定了私有的本地实例存取空间,从而为多线程环境常出现的并发访问问题提供了一种隔离机制。首先,我们需要知道,SessionFactory负责创建Session,SessionFactory是线程
安全的,多个并发线程可以同时访问一个SessionFactory并从中获取Session实例。而
Session并非线程安全,也就是说,如果多个线程同时使用一个Session实例进行数据存取,
则将会导致Session数据存取逻辑混乱。下面是一个典型的Servlet,我们试图通过一个类
变量session实现Session的重用,以避免每次操作都要重新创建:
publicclassTestServletextendsHttpServlet{
privateSessionsession;
publicvoiddoGet(HttpServletRequestrequest,
HttpServletResponseresponse)
throwsServletException,IOException{
session=getSession();
doSomething();
session.flush();
}
publicvoiddoSomething(){
......//基于session的存取操作
}
}
代码看上去正确无误,甚至在我们单机测试的时候可能也不会发生什么问题,但这样的代
HibernateDeveloper'sGuideVersion1.0
September2,2004Somanyopensourceprojects.WhynotOpenyourDocuments?
码一旦编译部署到实际运行环境中,接踵而来的莫名其妙的错误很可能会使得我们摸不找头脑。
问题出在哪里?
首先,Servlet运行是多线程的,而应用服务器并不会为每个线程都创建一个Servlet
实例,也就是说,TestServlet在应用服务器中只有一个实例(在Tomcat中是这样,其他的
应用服务器可能有不同的实现),而这个实例会被许多个线程并发调用,doGet方法也将被不
同的线程反复调用,可想而知,每次调用doGet方法,这个唯一的TestServlet实例的
session变量都会被重置,线程A的运行过程中,其他的线程如果也被执行,那么session
的引用将发生改变,之后线程A再调用session,可能此时的session与其之前所用的
session就不再一致,显然,错误也就不期而至。
ThreadLocal的出现,使得这个问题迎刃而解。
我们对上面的例子进行一些小小的修改:
publicclassTestServletextendsHttpServlet{
privateThreadLocallocalSession=newThreadLocal();
publicvoiddoGet(HttpServletRequestrequest,
HttpServletResponseresponse)
throwsServletException,IOException{
localSession.set(getSession());
doSomething();
session.flush();
}
publicvoiddoSomething(){
Sessionsession=(Session)localSession.get();
......//基于session的存取操作
}
}
可以看到,localSession是一个ThreadLocal类型的对象,在doGet方法中,我们
通过其set方法将获取的session实例保存,而在doSomething方法中,通过get方法取
出session实例。
这也就是ThreadLocal的独特之处,它会为每个线程维护一个私有的变量空间。实际上,
其实现原理是在JVM中维护一个Map,这个Map的key就是当前的线程对象,而value则是
线程通过ThreadLocal.set方法保存的对象实例。当线程调用ThreadLocal.get方法时,
ThreadLocal会根据当前线程对象的引用,取出Map中对应的对象返回。
这样,ThreadLocal通过以各个线程对象的引用作为区分,从而将不同线程的变量隔离开
来。
Hibernate官方开发手册的示例中,提供了一个通过ThreadLocal维护Session的好
榜样:
publicclassHibernateUtil{
privatestaticSessionFactorysessionFactory;
static{
try{
//CreatetheSessionFactory
sessionFactory=new
Configuration().configure().buildSessionFactory();
}catch(HibernateExceptionex){
thrownewRuntimeException(
"Configurationproblem:"+ex.getMessage(),
ex
);
}
}
publicstaticfinalThreadLocalsession=newThreadLocal();
publicstaticSessioncurrentSession()throwsHibernateException
{
Sessions=(Session)session.get();
//OpenanewSession,ifthisThreadhasnoneyet
if(s==null){
s=sessionFactory.openSession();
session.set(s);
}
returns;
}
publicstaticvoidcloseSession()throwsHibernateException{
Sessions=(Session)session.get();
session.set(null);
if(s!=null)
s.close();
}
}
在代码中,只要借助上面这个工具类获取Session实例,我们就可以实现线程范围内的
Session共享,从而避免了在线程中频繁的创建和销毁Session实例。不过注意在线程结束
时关闭Session。
同时值得一提的是,新版本的Hibernate在处理Session的时候已经内置了延迟加载机
制,只有在真正发生数据库操作的时候,才会从数据库连接池获取数据库连接,我们不必过于担
心Session的共享会导致整个线程生命周期内数据库连接被持续占用。
对于Web程序
而言,我们可以借助Servlet2.3规范中新引入的Filter机制,轻松实现线程生命周期内的
Session管理(关于Filter的具体描述,请参考Servlet2.3规范)。
Filter的生命周期贯穿了其所覆盖的Servlet(JSP也可以看作是一种特殊的Servlet)
及其底层对象。Filter在Servlet被调用之前执行,在Servlet调用结束之后结束。因此,
在Filter中管理Session对于Web程序而言就显得水到渠成。下面是一个通过Filter进
行Session管理的典型案例:
publicclassPersistenceFilterimplementsFilter
{
protectedstaticThreadLocalhibernateHolder=newThreadLocal();
publicvoiddoFilter(ServletRequestrequest,ServletResponse
response,FilterChainchain)
throwsIOException,ServletException
{
hibernateHolder.set(getSession());
try
{
......
chain.doFilter(request,response);
......
}
finally
{
Sessionsess=(Session)hibernateHolder.get();
if(sess!=null)
{
hibernateHolder.set(null);
try
{
sess.close();
}
catch(HibernateExceptionex){
thrownewServletException(ex);
}
}
}
}
......
HibernateDeveloper'sGuideVersion1.0
September2,2004Somanyopensourceprojects.WhynotOpenyourDocuments?
}
通过在doFilter中获取和关闭Session,并在周期内运行的所有对象(Filter链中其
余的Filter,及其覆盖的Servlet和其他对象)对此Session实例进行重用,保证了一个
HttpRequest处理过程中只占用一个Session,提高了整体性能表现。
在实际设计中,Session的重用做到线程级别一般已经足够,企图通过HttpSession实
现用户级的Session重用反而可能导致其他的问题。凡事不能过火,Session重用也一样。