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>

 

深入分析OpenSessionInViewFilter的内幕
最的在项目中发现Spring中对OpenSessionInViewFilter的作用,感觉很有意思,不由得自己的好奇心,开始研究起它的作用起来,总结如下:
Spring中对OpenSessionInViewFilter的描述如下:
     它是一个Servlet2.3过滤器,用来把一个Hibernate Session和一次完整的请求过程对应的线程相绑定。目的是为了实现"Open Session in View"的模式。例如: 它允许在事务提交之后延迟加载显示所需要的对象。
下面从处理请求的入口读起,下面所指的session均为hibernate session不再特别说明,本文观点纯属个人观点如有错误请批评指正不胜感激.

 

OpenSessionInViewFilter 的父类OncePerRequestFilter(抽象类)的方法,是过滤器的入口,是处理请求的第一个方法. 

public final void doFilter 
( ServletRequest request, ServletResponse response, FilterChain filterChain) 
        throws ServletException, IOException { 
    //首选判断进行过滤的是否是http请求 
    if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) { 
        throw new ServletException("OncePerRequestFilter just supports HTTP requests"); 
    } 
    //如果是http请求的话进行强转 
    HttpServletRequest httpRequest = (HttpServletRequest) request; 
    HttpServletResponse httpResponse = (HttpServletResponse) response; 
    //alreadyFilteredAttributeName 是一个标识,用于判断是否需要进行OpenSessionInViewFilter 
    String alreadyFilteredAttributeName = getAlreadyFilteredAttributeName(); 
    if (request.getAttribute(alreadyFilteredAttributeName) != null || shouldNotFilter(httpRequest)) { 
        // Proceed without invoking this filter... 
        filterChain.doFilter(request, response); 
    } 
    else { 
        // Do invoke this filter... 
        request.setAttribute(alreadyFilteredAttributeName, Boolean.TRUE); 
        //下面这个方法是abstract方法由OpenSessionInViewFilter 实现,是OpenSessionInViewFilter 的核心方法 
        doFilterInternal(httpRequest, httpResponse, filterChain); 
    } 

好下面让我们进入doFilterInternal一探究竟




protected void doFilterInternal(HttpServletRequest request, 
        HttpServletResponse response, FilterChain filterChain) 
        throws ServletException, IOException { 
    /** 
     * 从spring的上下文中取得SessionFactory对象 
     * WebApplicationContext wac = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext()); 
     * return (SessionFactory) wac.getBean(getSessionFactoryBeanName(),SessionFactory.class); 
     * getSessionFactoryBeanName()方法默认返回"sessionFactory"字符串,在spring配置文件中可要注意了,别写错了. 
     */ 
    SessionFactory sessionFactory = lookupSessionFactory(request); 
    boolean participate = false;// 标识过滤器结束时是否进行关闭session等后续处理 
    if (isSingleSession()) {//单session模式 
        //判断能否在当前线程中取得sessionFactory对象对应的session 
        if (TransactionSynchronizationManager.hasResource(sessionFactory)) { 
            //当能够找到session的时候证明会有相关类处理session的收尾工作,这个过滤器不能进行关闭session操作,否则会出现重复关闭的情况. 
            participate = true;//但我并没有想出正常使用的情况下什么时候会出现这种情况. 
        } else { 
            Session session = getSession(sessionFactory);//当前线程取不到session的时候通过sessionFactory创建,下面还会详细介绍此方法 
            //将session绑定到当前的线程中,SessionHolder是session的一层封装,里面有个存放session的map,而且是线程同步的Collections.synchronizedMap(new HashMap(1)); 
            //但是单session模式下一个SessionHolder对应一个session,核心方法是getValidatedSession 取得一个open状态下的session,并且取出后从map中移出. 
            TransactionSynchronizationManager.bindResource(sessionFactory, 
;                new SessionHolder(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等)的线程是采用线程池机制的,线程使用过后并不会销毁. 
                SessionHolder sessionHolder = (SessionHolder) TransactionSynchronizationManager 
                        .unbindResource(sessionFactory); 
                //取消绑定只是取消session对象和线程对象之间的引用,还没有关闭session,不关闭session相对于不关闭数据库连接,所以这里要关闭session 
                closeSession(sessionHolder.getSession(), sessionFactory); 
            } else { 
                //非单session模式,没有研究. 
                SessionFactoryUtils.processDeferredClose(sessionFactory); 
            } 
        } 
    } 

下面详细介绍TransactionSynchronizationManager的几个关键的方法



public abstract class TransactionSynchronizationManager { 
//线程局部变量,为每一个使用该变量的线程都提供一个变量值的副本,每一个线程都可以独立地改变自己的副本,而不会与其它线程的副本冲突,用于存放session 
private static final ThreadLocal resources = new ThreadLocal(); 
public static boolean hasResource(Object key) {//判断当前线程是否已经绑定了session,key是sessionFactory对象,一个sessionFactory可以绑定一个session 
    Assert.notNull(key, "Key must not be null");//spring的Assert类不错,大家可以看看很简单 
    Map map = (Map) resources.get(); 
    return (map != null && map.containsKey(key)); 

public static void bindResource(Object key, Object value) throws IllegalStateException {//绑定session到当前线程 
    Assert.notNull(key, "Key must not be null"); 
    Assert.notNull(value, "Value must not be null"); 
    Map map = (Map) resources.get();//ThreadLocal对象只可以存放一个对象,所以使用map来扩展 
    if (map == null) { 
        map = new HashMap(); 
        resources.set(map); 
    } 
    if (map.containsKey(key)) { 
        throw new IllegalStateException("Already value [" + map.get(key) + "] for key [" + key + 
                "] bound to thread [" + Thread.currentThread().getName() + "]"); 
    } 
    map.put(key, value); 

static Object unbindResource(Object key) throws IllegalStateException {//取消当前线程对session的绑定 
    Assert.notNull(key, "Key must not be null"); 
    Map map = (Map) resources.get(); 
    if (map == null || !map.containsKey(key)) { 
        throw new IllegalStateException( 
                "No value for key [" + key + "] bound to thread [" + Thread.currentThread().getName() + "]"); 
    } 
    Object value = map.remove(key); 
    if (map.isEmpty()) { 
        resources.set(null); 
    } 
    return value; 

另外一个非常关键的方法是OpenSessionInViewFilter的getSession方法,我们看这个方法的关键并不是如何取得session,而且注意这里设置了FlushMode 
protected Session getSession(SessionFactory sessionFactory) 
        throws DataAccessResourceFailureException { 
    Session session = SessionFactoryUtils.getSession(sessionFactory, true); 
    FlushMode flushMode = getFlushMode();//默认情况下是FlushMode.NEVER 
    if (flushMode != null) { 
        session.setFlushMode(flushMode); 
    } 
    return session; 

读到这个地方,大家对ThreadLocal 感兴趣的话,可以看下我以前写的一篇文章http://blog.csdn.net/sunyujia/archive/2008/06/15/2549564.aspx
FlushMode.NEVER:
调用Session的查询方法时,不清理缓存
调用Session.commit()时,不清理缓存
调用Session.flush()时,清理缓存
不过FlushMode.NEVER已经不再建议使用了
官方描述如下
Deprecated. use MANUAL instead. 使用FlushMode.MANUAL来代替
The Session is never flushed unless Session.flush() is explicitly called by the application. This mode is very efficient for read only transactions. 
直到调用Session.flush()才会将变化反应到数据库,在只读的情况下是效率非常高的 
详见http://www.hibernate.org/hib_docs/v3/api/org/hibernate/FlushMode.html
我们来细看SessionFactoryUtils.getSession(sessionFactory, true);



public static Session getSession(SessionFactory sessionFactory, boolean allowCreate) throws DataAccessResourceFailureException, IllegalStateException { 
    try { 
        return doGetSession(sessionFactory, null, null, allowCreate); 
    } 
    catch (HibernateException ex) { 
        throw new DataAccessResourceFailureException("Could not open Hibernate Session", ex); 
    } 

从上面可以看出doGetSession才是真正的核心方法,这里非常重要的是HibernateTemplate也是调用此方法



protected Session getSession() { 
    if (isAlwaysUseNewSession()) { 
        return SessionFactoryUtils.getNewSession(getSessionFactory(), getEntityInterceptor()); 
    }else if (!isAllowCreate()) { 
        return SessionFactoryUtils.getSession(getSessionFactory(), false); 
    }else { 
        return SessionFactoryUtils.getSession( 
                getSessionFactory(), getEntityInterceptor(), getJdbcExceptionTranslator()); 
    } 

无论如何调用最后都是落实到SessionFactoryUtils.doGetSession这个方法上面,无论这个方法最后是否返回了null实际上在方法中都取得了session,
这是整个流程中最复杂的一个方法,部分代码我理解的也很不好,大家一起研究研究.



private static Session doGetSession( 
        SessionFactory sessionFactory, Interceptor entityInterceptor, 
        SQLExceptionTranslator jdbcExceptionTranslator, boolean allowCreate) 
        throws HibernateException, IllegalStateException { 
    Assert.notNull(sessionFactory, "No SessionFactory specified"); 
    //取当前线程绑定 
的session 
    SessionHolder sessionHolder = (SessionHolder) TransactionSynchronizationManager.getResource(sessionFactory); 
    //sessionHolder就可以看成是当前线程绑定的session了 
    if (sessionHolder != null && !sessionHolder.isEmpty()) { 
        Session session = null; 
        if (TransactionSynchronizationManager.isSynchronizationActive() & 
                sessionHolder.doesNotHoldNonDefaultSession()) {//判断spring的事务管理是否是激活的,同时SessionHolder对象中有且仅有一个session 
            // Spring transaction management is active -> 
            // register pre-bound Session with it for transactional flushing. 
            session = sessionHolder.getValidatedSession(); 
            //在开事务的时候HibernateTransactionManager类中的doBegin方法会将isSynchronizedWithTransaction设置为true,暂时不知道什么情况下会进入如下代码块 
            if (session != null && !sessionHolder.isSynchronizedWithTransaction()) { 
                TransactionSynchronizationManager.registerSynchronization( 
                        new SpringSessionSynchronization(sessionHolder, sessionFactory, jdbcExceptionTranslator, false)); 
                sessionHolder.setSynchronizedWithTransaction(true); 
                // Switch to FlushMode.AUTO, as we have to assume a thread-bound Session 
                // with FlushMode.NEVER, which needs to allow flushing within the transaction. 
                FlushMode flushMode = session.getFlushMode(); 
                if (flushMode.lessThan(FlushMode.COMMIT) & 
                        !TransactionSynchronizationManager.isCurrentTransactionReadOnly()) { 
                    session.setFlushMode(FlushMode.AUTO); 
                    sessionHolder.setPreviousFlushMode(flushMode); 
                } 
            } 
        } 
        else { 
            // No Spring transaction management active -> try JTA transaction synchronization. 
            //在没用事务的情况下下面的方法中只调用了sessionHolder.getValidatedSession(); 
            session = getJtaSynchronizedSession(sessionHolder, sessionFactory, jdbcExceptionTranslator); 
        } 
        if (session != null) { 
            return session;//在使用了OpenSessionInViewFilter的情况下,HibernateTemplate执行此方法会在这里return 
        } 
    } 
    //如果当前线程没有绑定session那么无论如何都是要创建session的,但是否会return还要取决于allowCreate等条件,在后面会看到 
    Session session = (entityInterceptor != null ? 
            sessionFactory.openSession(entityInterceptor) : sessionFactory.openSession()); 

    // Use same Session for further Hibernate actions within the transaction. 
    // Thread object will get removed by synchronization at transaction completion. 
    //判断spring的事务管理是否是激活的,目前我分析结果是在单session情况下,只有OpenSessionInViewFilter调用此方法,才会执行到这里,这种情况下是不会有事务的. 
    //可见下面if块里面的代码是针对非单session,并且有事务的情况处理的. 
    if (TransactionSynchronizationManager.isSynchronizationActive())& ;{ 
        // We're within a Spring-managed transaction, possibly from JtaTransactionManager. 
        logger.debug("Registering Spring transaction synchronization for new Hibernate Session"); 
        SessionHolder holderToUse = sessionHolder; 
        if (holderToUse == null) { 
            holderToUse = new SessionHolder(session); 
        } 
        else { 
            holderToUse.addSession(session); 
        } 
        if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) { 
            session.setFlushMode(FlushMode.NEVER);//只读情况下,可以提高效率同时也防止了脏数据等 
        } 
        TransactionSynchronizationManager.registerSynchronization( 
                new SpringSessionSynchronization(holderToUse, sessionFactory, jdbcExceptionTranslator, true)); 
        holderToUse.setSynchronizedWithTransaction(true);//这个变量真是很费解不知道什么情况下会为false呢? 
        if (holderToUse != sessionHolder) {//从这里可以看出非单session情况下,有事务也是要绑定session的只是颗粒度不同而已,我猜非单session事务结束后session就被关闭了. 
            TransactionSynchronizationManager.bindResource(sessionFactory, holderToUse); 
        } 
    } 
    else { 
        // No Spring transaction management active -> try JTA transaction synchronization. 
        registerJtaSynchronization(session, sessionFactory, jdbcExceptionTranslator, sessionHolder); 
    } 

    // Check whether we are allowed to return the Session. 
    //校验能否创建session的动作居然是放在创建session之后处理的,有点不解. 
    if (!allowCreate && !isSessionTransactional(session, sessionFactory)) { 
        closeSession(session); 
        throw new IllegalStateException("No Hibernate Session bound to thread, " + 
            "and configuration does not allow creation of non-transactional one here"); 
    } 
    return session; 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值