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的,和过滤器的方式不同。