Spring解决Hibernate session 关闭
在你得 web.xml 文件里面加上下面的配置信息:
<filter> <filter-name>OpenSessionInViewFilter</filter-name> <filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class> </filter> <filter-mapping> <filter-name>OpenSessionInViewFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
Spring中对OpenSessionInViewFilter的描述如下:
它是一个Servlet2.3过滤器,用来把一个HibernateSession和一次完整的请求过程对应的线程相绑定。目的是为了实现"OpenSessioninView"的模式。例如:它允许在事务提交之后延迟加载显示所需要的对象。
下面从处理请求的入口读起,下面所指的session均为hibernatesession不再特别说明,本文观点纯属个人观点如有错误请批评指正不胜感激.
OpenSessionInViewFilter的父类OncePerRequestFilter(抽象类)的方法,是过滤器的入口,是处理请求的第一个方法.
publicfinalvoiddoFilter
(ServletRequestrequest,ServletResponseresponse,FilterChainfilterChain)
throwsServletException,IOException{
//首选判断进行过滤的是否是http请求
if(!(requestinstanceofHttpServletRequest)||!(responseinstanceofHttpServletResponse)){
thrownewServletException("OncePerRequestFilterjustsupportsHTTPrequests");
}
//如果是http请求的话进行强转
HttpServletRequesthttpRequest=(HttpServletRequest)request;
HttpServletResponsehttpResponse=(HttpServletResponse)response;
//alreadyFilteredAttributeName是一个标识,用于判断是否需要进行OpenSessionInViewFilter
StringalreadyFilteredAttributeName=getAlreadyFilteredAttributeName();
if(request.getAttribute(alreadyFilteredAttributeName)!=null||shouldNotFilter(httpRequest)){
//Proceedwithoutinvokingthisfilter...
filterChain.doFilter(request,response);
}
else{
//Doinvokethisfilter...
request.setAttribute(alreadyFilteredAttributeName,Boolean.TRUE);
//下面这个方法是abstract方法由OpenSessionInViewFilter实现,是OpenSessionInViewFilter的核心方法
doFilterInternal(httpRequest,httpResponse,filterChain);
}
}
好下面让我们进入doFilterInternal一探究竟
protectedvoiddoFilterInternal(HttpServletRequestrequest,
HttpServletResponseresponse,FilterChainfilterChain)
throwsServletException,IOException{
/**
*从spring的上下文中取得SessionFactory对象
*WebApplicationContextwac=WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext());
*return(SessionFactory)wac.getBean(getSessionFactoryBeanName(),SessionFactory.class);
*getSessionFactoryBeanName()方法默认返回"sessionFactory"字符串,在spring配置文件中可要注意了,别写错了.
*/
SessionFactorysessionFactory=lookupSessionFactory(request);
booleanparticipate=false;//标识过滤器结束时是否进行关闭session等后续处理
if(isSingleSession()){//单session模式
//判断能否在当前线程中取得sessionFactory对象对应的session
if(TransactionSynchronizationManager.hasResource(sessionFactory)){
//当能够找到session的时候证明会有相关类处理session的收尾工作,这个过滤器不能进行关闭session操作,否则会出现重复关闭的情况.
participate=true;//但我并没有想出正常使用的情况下什么时候会出现这种情况.
}else{
Sessionsession=getSession(sessionFactory);//当前线程取不到session的时候通过sessionFactory创建,下面还会详细介绍此方法
//将session绑定到当前的线程中,SessionHolder是session的一层封装,里面有个存放session的map,而且是线程同步的Collections.synchronizedMap(newHashMap(1));
//但是单session模式下一个SessionHolder对应一个session,核心方法是getValidatedSession取得一个open状态下的session,并且取出后从map中移出.
TransactionSynchronizationManager.bindResource(sessionFactory,
;newSessionHolder(session));
}
}else{//这段是非单session模式的处理情况,没有研究.但粗略看上去,大概思想是一样的
if(SessionFactoryUtils.isDeferredCloseActive(sessionFactory)){
participate=true;
}else{
SessionFactoryUtils.initDeferredClose(sessionFactory);
}
}
try{
//将session绑定到了当前线程后,就该处理请求了
filterChain.doFilter(request,response);
}finally{
if(!participate){
if(isSingleSession()){
//当请求结束时,对于单session模式,这时候要取消session的绑定,因为web容器(Tomcat等)的线程是采用线程池机制的,线程使用过后并不会销毁.
SessionHoldersessionHolder=(SessionHolder)TransactionSynchronizationManager
.unbindResource(sessionFactory);
//取消绑定只是取消session对象和线程对象之间的引用,还没有关闭session,不关闭session相对于不关闭数据库连接,所以这里要关闭session
closeSession(sessionHolder.getSession(),sessionFactory);
}else{
//非单session模式,没有研究.
SessionFactoryUtils.processDeferredClose(sessionFactory);
}
}
}
}
下面详细介绍TransactionSynchronizationManager的几个关键的方法
publicabstractclassTransactionSynchronizationManager{
//线程局部变量,为每一个使用该变量的线程都提供一个变量值的副本,每一个线程都可以独立地改变自己的副本,而不会与其它线程的副本冲突,用于存放session
privatestaticfinalThreadLocalresources=newThreadLocal();
publicstaticbooleanhasResource(Objectkey){//判断当前线程是否已经绑定了session,key是sessionFactory对象,一个sessionFactory可以绑定一个session
Assert.notNull(key,"Keymustnotbenull");//spring的Assert类不错,大家可以看看很简单
Mapmap=(Map)resources.get();
return(map!=null&&map.containsKey(key));
}
publicstaticvoidbindResource(Objectkey,Objectvalue)throwsIllegalStateException{//绑定session到当前线程
Assert.notNull(key,"Keymustnotbenull");
Assert.notNull(value,"Valuemustnotbenull");
Mapmap=(Map)resources.get();//ThreadLocal对象只可以存放一个对象,所以使用map来扩展
if(map==null){
map=newHashMap();
resources.set(map);
}
if(map.containsKey(key)){
thrownewIllegalStateException("Alreadyvalue["+map.get(key)+"]forkey["+key+
"]boundtothread["+Thread.currentThread().getName()+"]");
}
map.put(key,value);
}
staticObjectunbindResource(Objectkey)throwsIllegalStateException{//取消当前线程对session的绑定
Assert.notNull(key,"Keymustnotbenull");
Mapmap=(Map)resources.get();
if(map==null||!map.containsKey(key)){
thrownewIllegalStateException(
"Novalueforkey["+key+"]boundtothread["+Thread.currentThread().getName()+"]");
}
Objectvalue=map.remove(key);
if(map.isEmpty()){
resources.set(null);
}
returnvalue;
}
另外一个非常关键的方法是OpenSessionInViewFilter的getSession方法,我们看这个方法的关键并不是如何取得session,而且注意这里设置了FlushMode
protectedSessiongetSession(SessionFactorysessionFactory)
throwsDataAccessResourceFailureException{
Sessionsession=SessionFactoryUtils.getSession(sessionFactory,true);
FlushModeflushMode=getFlushMode();//默认情况下是FlushMode.NEVER
if(flushMode!=null){
session.setFlushMode(flushMode);
}
returnsession;
}
读到这个地方,大家对ThreadLocal感兴趣的话,可以看下我以前写的一篇文章http://blog.csdn.net/sunyujia/archive/2008/06/15/2549564.aspx
FlushMode.NEVER:
调用Session的查询方法时,不清理缓存
调用Session.commit()时,不清理缓存
调用Session.flush()时,清理缓存
不过FlushMode.NEVER已经不再建议使用了
官方描述如下
Deprecated.useMANUALinstead.使用FlushMode.MANUAL来代替
TheSessionisneverflushedunlessSession.flush()isexplicitlycalledbytheapplication.Thismodeisveryefficientforreadonlytransactions.
直到调用Session.flush()才会将变化反应到数据库,在只读的情况下是效率非常高的
详见http://www.hibernate.org/hib_docs/v3/api/org/hibernate/FlushMode.html
我们来细看SessionFactoryUtils.getSession(sessionFactory,true);
publicstaticSessiongetSession(SessionFactorysessionFactory,booleanallowCreate)throwsDataAccessResourceFailureException,IllegalStateException{
try{
returndoGetSession(sessionFactory,null,null,allowCreate);
}
catch(HibernateExceptionex){
thrownewDataAccessResourceFailureException("CouldnotopenHibernateSession",ex);
}
}
从上面可以看出doGetSession才是真正的核心方法,这里非常重要的是HibernateTemplate也是调用此方法
protectedSessiongetSession(){
if(isAlwaysUseNewSession()){
returnSessionFactoryUtils.getNewSession(getSessionFactory(),getEntityInterceptor());
}elseif(!isAllowCreate()){
returnSessionFactoryUtils.getSession(getSessionFactory(),false);
}else{
returnSessionFactoryUtils.getSession(
getSessionFactory(),getEntityInterceptor(),getJdbcExceptionTranslator());
}
}
无论如何调用最后都是落实到SessionFactoryUtils.doGetSession这个方法上面,无论这个方法最后是否返回了null实际上在方法中都取得了session,
这是整个流程中最复杂的一个方法,部分代码我理解的也很不好,大家一起研究研究.
privatestaticSessiondoGetSession(
SessionFactorysessionFactory,InterceptorentityInterceptor,
SQLExceptionTranslatorjdbcExceptionTranslator,booleanallowCreate)
throwsHibernateException,IllegalStateException{
Assert.notNull(sessionFactory,"NoSessionFactoryspecified");
//取当前线程绑定
的session
SessionHoldersessionHolder=(SessionHolder)TransactionSynchronizationManager.getResource(sessionFactory);
//sessionHolder就可以看成是当前线程绑定的session了
if(sessionHolder!=null&&!sessionHolder.isEmpty()){
Sessionsession=null;
if(TransactionSynchronizationManager.isSynchronizationActive()&
sessionHolder.doesNotHoldNonDefaultSession()){//判断spring的事务管理是否是激活的,同时SessionHolder对象中有且仅有一个session
//Springtransactionmanagementisactive->
//registerpre-boundSessionwithitfortransactionalflushing.
session=sessionHolder.getValidatedSession();
//在开事务的时候HibernateTransactionManager类中的doBegin方法会将isSynchronizedWithTransaction设置为true,暂时不知道什么情况下会进入如下代码块
if(session!=null&&!sessionHolder.isSynchronizedWithTransaction()){
TransactionSynchronizationManager.registerSynchronization(
newSpringSessionSynchronization(sessionHolder,sessionFactory,jdbcExceptionTranslator,false));
sessionHolder.setSynchronizedWithTransaction(true);
//SwitchtoFlushMode.AUTO,aswehavetoassumeathread-boundSession
//withFlushMode.NEVER,whichneedstoallowflushingwithinthetransaction.
FlushModeflushMode=session.getFlushMode();
if(flushMode.lessThan(FlushMode.COMMIT)&
!TransactionSynchronizationManager.isCurrentTransactionReadOnly()){
session.setFlushMode(FlushMode.AUTO);
sessionHolder.setPreviousFlushMode(flushMode);
}
}
}
else{
//NoSpringtransactionmanagementactive->tryJTAtransactionsynchronization.
//在没用事务的情况下下面的方法中只调用了sessionHolder.getValidatedSession();
session=getJtaSynchronizedSession(sessionHolder,sessionFactory,jdbcExceptionTranslator);
}
if(session!=null){
returnsession;//在使用了OpenSessionInViewFilter的情况下,HibernateTemplate执行此方法会在这里return
}
}
//如果当前线程没有绑定session那么无论如何都是要创建session的,但是否会return还要取决于allowCreate等条件,在后面会看到
Sessionsession=(entityInterceptor!=null?
sessionFactory.openSession(entityInterceptor):sessionFactory.openSession());
//UsesameSessionforfurtherHibernateactionswithinthetransaction.
//Threadobjectwillgetremovedbysynchronizationattransactioncompletion.
//判断spring的事务管理是否是激活的,目前我分析结果是在单session情况下,只有OpenSessionInViewFilter调用此方法,才会执行到这里,这种情况下是不会有事务的.
//可见下面if块里面的代码是针对非单session,并且有事务的情况处理的.
if(TransactionSynchronizationManager.isSynchronizationActive())&;{
//We'rewithinaSpring-managedtransaction,possiblyfromJtaTransactionManager.
logger.debug("RegisteringSpringtransactionsynchronizationfornewHibernateSession");
SessionHolderholderToUse=sessionHolder;
if(holderToUse==null){
holderToUse=newSessionHolder(session);
}
else{
holderToUse.addSession(session);
}
if(TransactionSynchronizationManager.isCurrentTransactionReadOnly()){
session.setFlushMode(FlushMode.NEVER);//只读情况下,可以提高效率同时也防止了脏数据等
}
TransactionSynchronizationManager.registerSynchronization(
newSpringSessionSynchronization(holderToUse,sessionFactory,jdbcExceptionTranslator,true));
holderToUse.setSynchronizedWithTransaction(true);//这个变量真是很费解不知道什么情况下会为false呢?
if(holderToUse!=sessionHolder){//从这里可以看出非单session情况下,有事务也是要绑定session的只是颗粒度不同而已,我猜非单session事务结束后session就被关闭了.
TransactionSynchronizationManager.bindResource(sessionFactory,holderToUse);
}
}
else{
//NoSpringtransactionmanagementactive->tryJTAtransactionsynchronization.
registerJtaSynchronization(session,sessionFactory,jdbcExceptionTranslator,sessionHolder);
}
//CheckwhetherweareallowedtoreturntheSession.
//校验能否创建session的动作居然是放在创建session之后处理的,有点不解.
if(!allowCreate&&!isSessionTransactional(session,sessionFactory)){
closeSession(session);
thrownewIllegalStateException("NoHibernateSessionboundtothread,"+
"andconfigurationdoesnotallowcreationofnon-transactionalonehere");
}
returnsession;
}