hibernate配置延迟加载报:failed to lazily initialize a collection of role: com.ln.jtf.biz.dal.dao.pojos.Customer.cusaccounts, no session or session was closed。原因是web上get子表时session已经关闭。针对这个问题spring提供了解决方案,有两种方式:
一种是过滤器:OpenSessionInViewFilter,在web.xml文件中增加配置如下:
<filter> <filter-name>hibernateFilter</filter-name> <filter-class> org.springframework.orm.hibernate3.support.OpenSessionInViewFilter </filter-class> </filter> <filter-mapping> <filter-name>hibernateFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
看一下代码实现:
protected void doFilterInternal( HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { SessionFactory sessionFactory = lookupSessionFactory(request); boolean participate = false; 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 OpenSessionInViewFilter"); Session 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); } } //..... }
通过ROOT WebApplicationContext已获取一个普通spring bean的方式来获取SessionFactory
protected SessionFactory lookupSessionFactory() { if (logger.isDebugEnabled()) { logger.debug("Using SessionFactory '" + getSessionFactoryBeanName() + "' for OpenSessionInViewFilter"); } WebApplicationContext wac = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext()); return wac.getBean(getSessionFactoryBeanName(), SessionFactory.class); }
再看如何获取到WebApplicationContext:
public static WebApplicationContext getWebApplicationContext(ServletContext sc, String attrName) { Assert.notNull(sc, "ServletContext must not be null"); Object attr = sc.getAttribute(attrName); if (attr == null) { return null; } if (attr instanceof RuntimeException) { throw (RuntimeException) attr; } if (attr instanceof Error) { throw (Error) attr; } if (attr instanceof Exception) { throw new IllegalStateException((Exception) attr); } if (!(attr instanceof WebApplicationContext)) { throw new IllegalStateException("Context attribute is not of type WebApplicationContext: " + attr); } return (WebApplicationContext) attr; }
其中attrName为:String ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE = WebApplicationContext.class.getName() + ".ROOT";
在某些配置下(?)由于根据这个attrName找不到对应的attr造成 sc.getAttribute(attrName)这个方法得到的是null,而报No WebApplicationContext found: no ContextLoaderListener registered?这个错误。
一种是配置拦截器:OpenSessionInViewInterceptor,在spring配置文件中增加如下(我使用的是Schema-based XML方式):
<mvc:interceptors> <bean id="openSessionInViewInterceptor" class="org.springframework.orm.hibernate3.support.OpenSessionInViewInterceptor"> <property name="sessionFactory" ref="sessionFactory"/> </bean> </mvc:interceptors>
这样配置之后session将会保持。
看一下代码实现:
public void preHandle(WebRequest request) throws DataAccessException { if ((isSingleSession() && TransactionSynchronizationManager.hasResource(getSessionFactory())) || SessionFactoryUtils.isDeferredCloseActive(getSessionFactory())) { // Do not modify the Session: just mark the request accordingly. String participateAttributeName = getParticipateAttributeName(); Integer count = (Integer) request.getAttribute(participateAttributeName, WebRequest.SCOPE_REQUEST); int newCount = (count != null ? count + 1 : 1); request.setAttribute(getParticipateAttributeName(), newCount, WebRequest.SCOPE_REQUEST); } else { if (isSingleSession()) { // single session mode logger.debug("Opening single Hibernate Session in OpenSessionInViewInterceptor"); Session session = SessionFactoryUtils.getSession( getSessionFactory(), getEntityInterceptor(), getJdbcExceptionTranslator()); applyFlushMode(session, false); TransactionSynchronizationManager.bindResource(getSessionFactory(), new SessionHolder(session)); } else { // deferred close mode SessionFactoryUtils.initDeferredClose(getSessionFactory()); } } }
可以看到在这里spring做了判断,如果当前请求线程中没有绑定session,那么就新创建一个,并且绑定到当前线程中。特别要提一下这里是通过HibernateAccessor这个InitializingBean来获取到SessionFactory的,和过滤器的方式不同。