再談Hibernate the owing session was closed

ERROR] - could not initialize proxy - the owning Session was closed - [org.hibernate.LazyInitializationException.<init>(LazyInitializationException.java:19)]

用結合spring 使用hibernate都會遇到的問題, 爲了避免多餘的關聯查詢, lazy loading的引入, 但是通常持久層會把session關閉了, render view的時候PO 裏面的session其實已經關閉了, 再load其他關聯屬性, 異常就出來了.

 

單獨使用hibernate的事務管理吧. http://www.hibernate.org/42.html

 

 

 以下討論話題都是基於Spring + Hibernate配套使用.

 

1. session在哪裏給關閉了?

(1) 假設沒有配置OpenSessionInViewFilter, 以及HibernateTransactionManager,AOP任何事務。 我們只是使用HibernateTemplate 在DAO層裏做若干次查詢操作, 跟蹤日誌.

2009-03-11 14:28:51,759 [DEBUG] - #{genericFO.signIn} - [com....web.AccessControlActionListenerImpl.preProcessAction(AccessControlActionListenerImpl.java:66)]
2009-03-11 14:28:52,259 [DEBUG] - Opening Hibernate Session - [org.springframework.orm.hibernate3.SessionFactoryUtils.doGetSession(SessionFactoryUtils.java:316)]
2009-03-11 14:28:52,852 [DEBUG] - Eagerly flushing Hibernate session - [org.springframework.orm.hibernate3.HibernateAccessor.flushIfNecessary(HibernateAccessor.java:389)]
2009-03-11 14:28:52,868 [DEBUG] - Closing Hibernate Session - [org.springframework.orm.hibernate3.SessionFactoryUtils.closeSession(SessionFactoryUtils.java:772)]
2009-03-11 14:28:52,899 [DEBUG] - Opening Hibernate Session - [org.springframework.orm.hibernate3.SessionFactoryUtils.doGetSession(SessionFactoryUtils.java:316)]
2009-03-11 14:28:53,040 [DEBUG] - Eagerly flushing Hibernate session - [org.springframework.orm.hibernate3.HibernateAccessor.flushIfNecessary(HibernateAccessor.java:389)]
2009-03-11 14:28:53,040 [DEBUG] - Closing Hibernate Session - [org.springframework.orm.hibernate3.SessionFactoryUtils.closeSession(SessionFactoryUtils.java:772)]
2009-03-11 14:28:53,056 [ERROR] - could not initialize proxy - the owning Session was closed - [org.hibernate.LazyInitializationException.<init>(LazyInitializationException.java:19)]

 

SessionFactoryUtils line 316

  logger.debug("Opening Hibernate Session");
  Session session = (entityInterceptor != null ?
    sessionFactory.openSession(entityInterceptor) : sessionFactory.openSession());

 可以跟一下實現類SessionFactoryImpl, openSession可是每次new 一個Session。

 private SessionImpl openSession(
  Connection connection,
     boolean autoClose,
     long timestamp,
     Interceptor sessionLocalInterceptor
 ) {
  return new SessionImpl(
          connection,
          this,
          autoClose,
          timestamp,
          sessionLocalInterceptor == null ? interceptor : sessionLocalInterceptor,
          settings.getDefaultEntityMode(),
          settings.isFlushBeforeCompletionEnabled(),
          settings.isAutoCloseSessionEnabled(),
          settings.getConnectionReleaseMode()
   );
 }

SessionFactory的openSession和getCurrentSession區別比較大的, 一般currentSession有個實現是和threadLocal綁定的, 也就是說每個請求多次查詢數據庫可能使用的都是同一個session.

心疼了吧, 什麼都不配置直接使用HibernateTemplate是比較耗的.  而且currentSession的實現在事務之後會保證session關閉(至少CurrentSessionContext這個接口是這樣定義的)

 

順便瞄一下HibernateTemplate查詢調用的代碼吧.

 /**
  * Execute the action specified by the given action object within a Session.
  * @param action callback object that specifies the Hibernate action
  * @param exposeNativeSession whether to expose the native Hibernate Session
  * to callback code
  * @return a result object returned by the action, or <code>null</code>
  * @throws org.springframework.dao.DataAccessException in case of Hibernate errors
  */
 public Object execute(HibernateCallback action, boolean exposeNativeSession) throws DataAccessException {
  Assert.notNull(action, "Callback object must not be null");

  Session session = getSession();//每次都是openSession, 因爲ThreadLocal沒同一個事務的session
  boolean existingTransaction = SessionFactoryUtils.isSessionTransactional(session, getSessionFactory());//爲false, 因爲threadLocal沒有session
  if (existingTransaction) {
   logger.debug("Found thread-bound Session for HibernateTemplate");
  }

  FlushMode previousFlushMode = null;
  try {
   previousFlushMode = applyFlushMode(session, existingTransaction);
   enableFilters(session);
   Session sessionToExpose = (exposeNativeSession ? session : createSessionProxy(session));
   Object result = action.doInHibernate(sessionToExpose);
   flushIfNecessary(session, existingTransaction);
   return result;
  }
  catch (HibernateException ex) {
   throw convertHibernateAccessException(ex);
  }
  catch (SQLException ex) {
   throw convertJdbcAccessException(ex);
  }
  catch (RuntimeException ex) {
   // Callback code threw application exception...
   throw ex;
  }
  finally {
   if (existingTransaction) {
    logger.debug("Not closing pre-bound Hibernate Session after HibernateTemplate");
    disableFilters(session);
    if (previousFlushMode != null) {
     session.setFlushMode(previousFlushMode);
    }
   }
   else {
    // Never use deferred close for an explicitly new Session.
    if (isAlwaysUseNewSession()) {//默認配置alwaysUseNewSession=false
     SessionFactoryUtils.closeSession(session);
    }
    else {//每次都在這裏關閉
     SessionFactoryUtils.closeSessionOrRegisterDeferredClose(session, getSessionFactory());
    }
   }
  }
 }

 

(2) 那我們代碼事務或用AOP配置的聲明事務看下,主要就是看下HibernateTransactionmanager的使用了其實。假設使用的是TransactionDefinition.PROPAGATION_REQUIRED

public final TransactionStatus getTransaction(TransactionDefinition definition) {

}

void commit(TransactionStatus status) {

 ..processCommit(DefaultTransactionStatus status);...

}

processCommit(DefaultTransactionStatus status) {...

  finally {
   cleanupAfterCompletion(status);
  }

}

 private void cleanupAfterCompletion(DefaultTransactionStatus status) {
  status.setCompleted();
  if (status.isNewSynchronization()) {
   TransactionSynchronizationManager.clear();
  }
  if (status.isNewTransaction()) {
   doCleanupAfterCompletion(status.getTransaction());
  }
  if (status.getSuspendedResources() != null) {
   if (status.isDebug()) {
    logger.debug("Resuming suspended transaction");
   }
   resume(status.getTransaction(), (SuspendedResourcesHolder) status.getSuspendedResources());
  }
 }

 

其實調用的是Hibernate Session的Transaction, 默認的應該就是JDBCTransaction

 

 protected void doCleanupAfterCompletion(Object transaction) {
  HibernateTransactionObject txObject = (HibernateTransactionObject) transaction;

  // Remove the session holder from the thread.
  if (txObject.isNewSessionHolder()) {
   TransactionSynchronizationManager.unbindResource(getSessionFactory());
  }

  // Remove the JDBC connection holder from the thread, if exposed.
  if (getDataSource() != null) {
   TransactionSynchronizationManager.unbindResource(getDataSource());
  }

  Session session = txObject.getSessionHolder().getSession();
  if (this.prepareConnection && session.isConnected() && isSameConnectionForEntireSession(session)) {
   // We're running with connection release mode "on_close": We're able to reset
   // the isolation level and/or read-only flag of the JDBC Connection here.
   // Else, we need to rely on the connection pool to perform proper cleanup.
   try {
    Connection con = session.connection();
    DataSourceUtils.resetConnectionAfterTransaction(con, txObject.getPreviousIsolationLevel());
   }
   catch (HibernateException ex) {
    logger.debug("Could not access JDBC Connection of Hibernate Session", ex);
   }
  }

  if (txObject.isNewSessionHolder()) {//如果沒用OpenSessionInViewFilter,這個txObject是newSessionHolder=true,否則爲false
   if (logger.isDebugEnabled()) {
    logger.debug("Closing Hibernate Session [" + SessionFactoryUtils.toString(session) +
      "] after transaction");
   }//deferredClose空,所以還是關了session
   SessionFactoryUtils.closeSessionOrRegisterDeferredClose(session, getSessionFactory());
  }
  else {
   if (logger.isDebugEnabled()) {
    logger.debug("Not closing pre-bound Hibernate Session [" +
      SessionFactoryUtils.toString(session) + "] after transaction");
   }
   if (txObject.getSessionHolder().getPreviousFlushMode() != null) {
    session.setFlushMode(txObject.getSessionHolder().getPreviousFlushMode());
   }
   if (hibernateSetTimeoutAvailable) {
    // Running against Hibernate 3.1+...
    // Let's explicitly disconnect the Session to provide efficient Connection handling
    // even with connection release mode "on_close". The Session will automatically
    // obtain a new Connection in case of further database access.
    // Couldn't do this on Hibernate 3.0, where disconnect required a manual reconnect.
    session.disconnect();
   }
  }
  txObject.getSessionHolder().clear();
 }

 可以簡單這樣認爲, 由於事務是新的(因爲ThreadLocal的SessionHolder是新創的,事先不存在), 事務完成後session還是關了.

 

 

其實session關了是好習慣啊,就好像用完了連接就關閉一樣。

 

2. 怎麼讓session在view也是active?

OpenSessionInViewFilter,

 

 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));

//ThreadLocal保存一個SessionHolder, 之後在事務HibernateTransactionManager中就認爲NewSessionHolder=false, 清理的時候就關不了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);
   }
  }

  try {
   filterChain.doFilter(request, response);//下一個filter, 如果沒filter就是要採訪的jsp/servlet了
  }

  finally {
   if (!participate) {
    if (isSingleSession()) {
     // single session mode
     SessionHolder sessionHolder =
       (SessionHolder) TransactionSynchronizationManager.unbindResource(sessionFactory);
     logger.debug("Closing single Hibernate Session in OpenSessionInViewFilter");
     closeSession(sessionHolder.getSession(), sessionFactory);
    }
    else {
     // deferred close mode
     SessionFactoryUtils.processDeferredClose(sessionFactory);
    }
   }
  }
 }

 

很多人說的一個詬病就是session跨多個層, 特別是在頁面渲染的時候, 寫到客戶端時pending時間長, session資源沒及時釋放, 或者有一定的道理, 很可惜沒看到數據證明。

 

老實說, 這麼一個問題的引入也證明一點, 沒什麼框架是完美的真那麼傻瓜的, 適合使用, 簡單就好。

Hibernate也只是我們項目中持久層的一個應用框架.

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章