用 OpenSessionInViewInterceptor 的思路解决 Spring框架中的Hibernate Lazy

 

众所周知, 为了解决 Hibernate Lazy 问题, Spring 中引入了 OpenSessionInViewInterceptor, 这样虽然解决了页面上的 Lazy Load 问题,却增加了各层之间的偶合性,
如果一个 Lazy 的 Collection 在页面上可以被正确的 load, 但是如果请求不是来自于 HttpServletRequest (比如在 TestCase 或 Service 中希望获取 lazy 的属性),
一般会导致两种错误:

代码
 
01.1. 设置了 lazy = "true"  
02.   会导致 org.hibernate.LazyInitializationException: failed to lazily initialize a collection of xxx: xxx - no session or session was closed   
03.2. 设置里 lazy = "false"  
04.   会导致 org.hibernate.LazyInitializationException: could not initialize proxy - the owning Session was closed   

为了方便测试, 灵活使用 lazy load, 我按照 OpenSessionInViewInterceptor 的思路实现了一个 HibernateLazyResolber, 代码如下:

 
package cn.ccut312.common.daobase;

  
import org.apache.commons.logging.Log;   
import org.apache.commons.logging.LogFactory;   
import org.hibernate.FlushMode;   
import org.hibernate.Session;   
import org.hibernate.SessionFactory;   
import org.springframework.beans.factory.InitializingBean;   
import org.springframework.dao.DataAccessResourceFailureException;   
import org.springframework.orm.hibernate3.SessionFactoryUtils;
import org.springframework.orm.hibernate3.SessionHolder;
import org.springframework.transaction.support.TransactionSynchronizationManager;   
  
/**   
 * <class>HibernateLazyResolver</class> 用于模拟 OpenSessionInViewInterceptor, 它可以被任意使用而不依赖于 Web 环境   
 *    
 * @see org.springframework.orm.hibernate.support.OpenSessionInViewInterceptor   
 * @see org.springframework.orm.hibernate.support.OpenSessionInViewFilter   
 * @since --   
 * @author 王政   
 * @version $Id: HibernateLazyResolver.java,v . // :: Administrator Exp $   
 */   
public class HibernateLazyResolver implements InitializingBean {   
  
    private static Log logger = LogFactory.getLog(HibernateLazyResolver.class);   
  
    private boolean singleSession = true;    
  
    private SessionFactory sessionFactory;   
  
    boolean participate = false;   
  
    protected Session session = null;   
          
    public final void setSessionFactory(SessionFactory sessionFactory) {   
        this.sessionFactory = sessionFactory;   
    }   
  
    /**   
    * Set whether to use a single session for each request. Default is true.   
    * <p>If set to false, each data access operation or transaction will use   
    * its own session (like without Open Session in View). Each of those   
    * sessions will be registered for deferred close, though, actually   
    * processed at request completion.   
    * @see SessionFactoryUtils#initDeferredClose   
    * @see SessionFactoryUtils#processDeferredClose   
    */   
    public void setSingleSession(boolean singleSession) {   
        this.singleSession = singleSession;   
    }   
  
    /**   
    * Return whether to use a single session for each request.   
    */   
    protected boolean isSingleSession() {   
        return singleSession;   
    }   
       
    public void afterPropertiesSet() throws Exception {   
        if (sessionFactory == null) {   
            throw new IllegalArgumentException("SessionFactory is reqirued!");   
        }   
    }   
  
    /**   
     * 初始化 session, 在需要 lazy 的开始处调用   
     *   
     */   
    public void openSession() {   
        if (isSingleSession()) {   
            // single session mode   
            if (TransactionSynchronizationManager.hasResource(sessionFactory)) {   
                // Do not modify the Session: just set the participate flag.   
                participate = true;   
            }   
            else {   
                logger.debug("Opening single Hibernate Session in HibernateLazyResolver");   
                session = getSession(sessionFactory);   
                TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session));   
            }   
        }   
        else {   
            // deferred close mode   
            if (SessionFactoryUtils.isDeferredCloseActive(sessionFactory)) {   
                // Do not modify deferred close: just set the participate flag.   
                participate = true;   
            }   
            else {   
                SessionFactoryUtils.initDeferredClose(sessionFactory);   
            }   
        }   
           
    }   
  
    /**   
     * 释放 session, 在 lazy 的结束处调用   
     *   
     */   
    public void releaseSession() {   
        if (!participate) {   
            if (isSingleSession()) {   
               // single session mode   
                TransactionSynchronizationManager.unbindResource(sessionFactory);   
                logger.debug("Closing single Hibernate Session in HibernateLazyResolver");   
                try {   
                    closeSession(session, sessionFactory);   
                }   
                catch (RuntimeException ex) {   
                    logger.error("Unexpected exception on closing Hibernate Session", ex);   
                }   
            }   
            else {   
                // deferred close mode   
                SessionFactoryUtils.processDeferredClose(sessionFactory);   
            }   
        }   
    }   
           
    /**   
     * Get a Session for the SessionFactory that this filter uses.   
     * Note that this just applies in single session mode!   
     * <p>The default implementation delegates to SessionFactoryUtils'   
     * getSession method and sets the Session's flushMode to NEVER.   
     * <p>Can be overridden in subclasses for creating a Session with a custom   
     * entity interceptor or JDBC exception translator.   
     * @param sessionFactory the SessionFactory that this filter uses   
     * @return the Session to use   
     * @throws DataAccessResourceFailureException if the Session could not be created   
     * @see org.springframework.orm.hibernate.SessionFactoryUtils#getSession(SessionFactory, boolean)   
     * @see org.hibernate.FlushMode#NEVER   
     */   
    protected Session getSession(SessionFactory sessionFactory) throws DataAccessResourceFailureException {   
        Session session = SessionFactoryUtils.getSession(sessionFactory, true);   
        // 注意这里与 OpenSessionInViewInterceptor 不同, 需要设置为 auto, 否则会导致以下异常   
        // org.springframework.dao.InvalidDataAccessApiUsageException:    
        // Write operations are not allowed in read-only mode (FlushMode.NEVER) -    
        // turn your Session into FlushMode.AUTO or remove 'readOnly' marker from transaction definition   
        session.setFlushMode(FlushMode.AUTO);   
        return session;   
    }   
  
    /**   
     * Close the given Session.   
     * Note that this just applies in single session mode!   
     * <p>The default implementation delegates to SessionFactoryUtils'   
     * releaseSession method.   
     * <p>Can be overridden in subclasses, e.g. for flushing the Session before   
     * closing it. See class-level javadoc for a discussion of flush handling.   
     * Note that you should also override getSession accordingly, to set   
     * the flush mode to something else than NEVER.   
     * @param session the Session used for filtering   
     * @param sessionFactory the SessionFactory that this filter uses   
     */   
    protected void closeSession(Session session, SessionFactory sessionFactory) {  
    	session.flush();
    	SessionFactoryUtils.releaseSession(session, sessionFactory);
    }   
}   
 

使用方法, 在配置文件中声明

 

代码
 
<!-- use to resolve hibernate lazy load -->  
<bean id="hibernateLazyResolver" class="org.summerfragrance.support.hibernate3.HibernateLazyResolver">  
     <property name="sessionFactory"><ref local="sessionFactory"/></property>  
</bean>    
 
<bean id="userManager" parent="txProxyTemplate">  
     <property name="target">  
          <bean class="org.summerfragrance.security.service.impl.UserManagerImpl" parent="managerTarget">  
               <property name="userDAO"><ref bean="userDAO"/></property>  
           <property name="hibernateLazyResolver"><ref bean="hibernateLazyResolver"/></property>  
          </bean>  
       </property>  
  </bean>  

然后在代码中这样调用

 

hibernateLazyResolver.openSession();   
 
....   
//需要 lazy load 的代码   
  
hibernateLazyResolver.releaseSession();   


 

如果是 TestCase, 可以简单的设置 BaseTestCase 如下

代码
  1.   
  2. package org.summerfragrance;   
  3.   
  4. import junit.framework.TestCase;   
  5.   
  6. import org.apache.commons.logging.Log;   
  7. import org.apache.commons.logging.LogFactory;   
  8. import org.springframework.context.ApplicationContext;   
  9. import org.springframework.context.support.ClassPathXmlApplicationContext;   
  10. import org.summerfragrance.support.hibernate3.HibernateLazyResolver;   
  11.   
  12. /**  
  13.  * Base class for running DAO tests.  
  14.  *   
  15.  * @author mraible  
  16.  */  
  17. public class BaseTestCase extends TestCase {   
  18.   
  19.     protected final Log log = LogFactory.getLog(getClass());   
  20.   
  21.     protected final static ApplicationContext ctx;   
  22.   
  23.     protected HibernateLazyResolver hibernateLazyResolver;   
  24.   
  25.     static {   
  26.         String[] paths = { "/conf/applicationContext-dataSource.xml",   
  27.                 "/org/summerfragrance/vfs/applicationContext-vfs.xml",   
  28.                 "/org/summerfragrance/security/dao/hibernate/applicationContext-hibernate.xml"  
  29.         // "/org/summerfragrance/security/dao/jdbc/applicationContext-jdbc.xml"   
  30.         };   
  31.         ctx = new ClassPathXmlApplicationContext(paths);   
  32.     }   
  33.   
  34.     /**  
  35.      * @see junit.framework.TestCase#setUp()  
  36.      */  
  37.     protected void setUp() throws Exception {   
  38.         super.setUp();   
  39.         hibernateLazyResolver = (HibernateLazyResolver) ctx   
  40.                 .getBean("hibernateLazyResolver");   
  41.         hibernateLazyResolver.openSession();   
  42.     }   
  43.   
  44.     /**  
  45.      * @see junit.framework.TestCase#tearDown()  
  46.      */  
  47.     protected void tearDown() throws Exception {   
  48.         super.tearDown();   
  49.         hibernateLazyResolver.releaseSession();   
  50.         hibernateLazyResolver = null;   
  51.     }   
  52.   }     

 

这样就可以在 Service 和 TestCase 中使用 Lazy Load 了, 目前已经测试通过

这几天看 JavaEye 上关于 OpenSessionInView 的讨论, 感觉这个问题比较常见

 

代码
  1.   
  2.   在代码中调用 openSession(), 然后不予处理, 这就是 ajoo 说的第一种不擦屁股就直接走人的做法, 这样可能导致两种错误;   
  3.    a. org.springframework.orm.hibernate3.HibernateSystemException: Illegal attempt to associate a collection with two open sessions   
  4.    b. 数据库连接不关闭   
  5.    正确的做法是用 HibernateCallBack 或者参照 HibernateTemplate 对 session 进行处理   
  6.   

 

以上都是一些个人想法, 小弟学习 Hibernate 不久, 欢迎各位拍转

 

 

代码
 
 

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值