"Session was already closed" 。
出錯的問題在於DAO的實作都用this.getSession() ,(All Dao Layer class extends HibernateDaoSupport)
來取得hibernate Session ,但Service & DAO 的Reference 關系很復雜。
(Spring 的文件中提到, spring 的Session是一個thread-bound Session,它是和某個Thread绑定的,而這個Thread往往就是載入Servlet/Jsp的那的thread,實際的意思就是其生命周期scope是request/response的。
getSession取得了Hibernate的Session,這個Session可能是當前request中之前使用過的,也可能是一個新的,this.getSession()就有可能造成transaction 在commit的時候closed session出錯)
雖然繼承了HibernateDaoSupport這個類,但是this.getSession(),獲得的session也要在使用後關閉,因為這個session是原生的session不是經過sping代理過的,並且還沒有Transaction,自動提交,自動關閉連接等功能,所以使用使用getSession()獲得session時一定要關閉。
Hibernate documentation 提到的 Session Object
A Session is an inexpensive, non-threadsafe object that should be used once and then discarded for: a single request, a conversation or a single unit of work. A Session will not obtain a JDBC Connection, or a Datasource, unless it is needed. It will not consume any resources until used.
Do not use the session-per-operation antipattern,
The most common pattern in a multi-user client/server application is session-per-request.
HiberDaoSupport代碼
/** * Obtain a Hibernate Session, either from the current transaction or * a new one. The latter is only allowed if "allowCreate" is true. * <p><b>Note that this is not meant to be invoked from HibernateTemplate code * but rather just in plain Hibernate code.</b> Either rely on a thread-bound * Session or use it in combination with {@link #releaseSession}. * <p>In general, it is recommended to use * {@link #getHibernateTemplate() HibernateTemplate}, either with * the provided convenience operations or with a custom * {@link org.springframework.orm.hibernate3.HibernateCallback} that * provides you with a Session to work on. HibernateTemplate will care * for all resource management and for proper exception conversion. * @param allowCreate if a non-transactional Session should be created when no * transactional Session can be found for the current thread * @return the Hibernate Session * @throws DataAccessResourceFailureException if the Session couldn't be created * @throws IllegalStateException if no thread-bound Session found and allowCreate=false * @see org.springframework.orm.hibernate3.SessionFactoryUtils#getSession(SessionFactory, boolean) */ protected final Session getSession(boolean allowCreate) throws DataAccessResourceFailureException, IllegalStateException { return (!allowCreate ? SessionFactoryUtils.getSession(getSessionFactory(), false) : SessionFactoryUtils.getSession( getSessionFactory(), this.hibernateTemplate.getEntityInterceptor(), this.hibernateTemplate.getJdbcExceptionTranslator())); }
allowCreate default 數值為true , this.getSession()能從當前的事務或新的事務取的一個新的Hibernate session object,this.getHibernateTemplate().getSessionFactory().getCurrentSession()/openSession()則從spring中獲取session
getCurrentSession()創建的Session會綁定到當前的線程中去、而採用OpenSession()則不會。
採用getCurrentSession()創建的Session在commit或rollback後會自動關閉,採用OpenSession()必須手動關閉。
看看HibernateTemplate
public List find(final String queryString, final Object... values) throws DataAccessException { return executeWithNativeSession(new HibernateCallback<List>() { public List doInHibernate(Session session) throws HibernateException { Query queryObject = session.createQuery(queryString); prepareQuery(queryObject); if (values != null) { for (int i = 0; i < values.length; i ) { queryObject.setParameter(i, values[i]); } } return queryObject.list(); } }); } /** * Execute the action specified by the given action object within a * native {@link org.hibernate.Session}. * <p>This execute variant overrides the template-wide * {@link #isExposeNativeSession() "exposeNativeSession"} setting. * @param action callback object that specifies the Hibernate action * @return a result object returned by the action, or <code>null</code> * @throws org.springframework.dao.DataAccessException in case of Hibernate errors */ public <T> T executeWithNativeSession(HibernateCallback<T> action) { return doExecute(action, false, true); } /** * Execute the action specified by the given action object within a Session. * @param action callback object that specifies the Hibernate action * @param enforceNewSession whether to enforce a new Session for this template * even if there is a pre-bound transactional Session * @param enforceNativeSession whether to enforce exposure of 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 */ protected <T> T doExecute(HibernateCallback<T> action, boolean enforceNewSession, boolean enforceNativeSession) throws DataAccessException { Assert.notNull(action, "Callback object must not be null"); Session session = (enforceNewSession ? SessionFactoryUtils.getNewSession(getSessionFactory(), getEntityInterceptor()) : getSession()); boolean existingTransaction = (!enforceNewSession && (!isAllowCreate() || SessionFactoryUtils.isSessionTransactional(session, getSessionFactory()))); if (existingTransaction) { logger.debug("Found thread-bound Session for HibernateTemplate"); } FlushMode previousFlushMode = null; try { previousFlushMode = applyFlushMode(session, existingTransaction); enableFilters(session); Session sessionToExpose = (enforceNativeSession || isExposeNativeSession() ? session : createSessionProxy(session)); T 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()) { SessionFactoryUtils.closeSession(session); } else { SessionFactoryUtils.closeSessionOrRegisterDeferredClose(session, getSessionFactory()); } } } }
再來重要的就是HibernateTransactionManager, 在doBegin 裡面, HibernateTransactionObject的一個實例,這個實例裡主要存放的就是sessionholder,sessionholder裡存放的就是開始事務的session and Transaction,如果之前沒有 sessionholder存放到thread,那麼這個 HibernateTransactionObject的實例的屬性其實是空的。
如果Transaction中並沒有存放sessionholder,那麼就新建一個session,放到新的sessionholder中,再放到HibernateTransactionObject的實例中。
如果給service設置聲明式Transaction,假設Transaction為required,然後一個service調用另一個service時,他們其實是共用一個session,原則是沒有就create,反之則不create,並返回之前已create的session和transaction。 也就是說spring通過threadlocal把session和對應的transaction放到線程之中,保證了在整個方法棧的任何一個地方都能得到同一個session和transaction。
所以如果你的方法在事務體之內,那麼你只要通過hibernatesupportdao或者hibernatetemplate來得到session的話,那這個session一定是開始事務的那個session,這個得到session的主要方法在SessionFactoryUtils裡。
HibernateTransactionManager
@Override protected void doBegin(Object transaction, TransactionDefinition definition) { HibernateTransactionObject txObject = (HibernateTransactionObject) transaction; if (txObject.hasConnectionHolder() && !txObject.getConnectionHolder().isSynchronizedWithTransaction()) { throw new IllegalTransactionStateException( "Pre-bound JDBC Connection found! HibernateTransactionManager does not support " "running within DataSourceTransactionManager if told to manage the DataSource itself. " "It is recommended to use a single HibernateTransactionManager for all transactions " "on a single DataSource, no matter whether Hibernate or JDBC access."); } Session session = null; try { if (txObject.getSessionHolder() == null || txObject.getSessionHolder().isSynchronizedWithTransaction()) { Interceptor entityInterceptor = getEntityInterceptor(); Session newSession = (entityInterceptor != null ? getSessionFactory().openSession(entityInterceptor) : getSessionFactory().openSession()); if (logger.isDebugEnabled()) { logger.debug("Opened new Session [" SessionFactoryUtils.toString(newSession) "] for Hibernate transaction"); } txObject.setSession(newSession); } session = txObject.getSessionHolder().getSession(); if (this.prepareConnection && isSameConnectionForEntireSession(session)) { // We're allowed to change the transaction settings of the JDBC Connection. if (logger.isDebugEnabled()) { logger.debug( "Preparing JDBC Connection of Hibernate Session [" SessionFactoryUtils.toString(session) "]"); } Connection con = session.connection(); Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition); txObject.setPreviousIsolationLevel(previousIsolationLevel); } else { // Not allowed to change the transaction settings of the JDBC Connection. if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) { // We should set a specific isolation level but are not allowed to... throw new InvalidIsolationLevelException( "HibernateTransactionManager is not allowed to support custom isolation levels: " "make sure that its 'prepareConnection' flag is on (the default) and that the " "Hibernate connection release mode is set to 'on_close' (SpringTransactionFactory's default). " "Make sure that your LocalSessionFactoryBean actually uses SpringTransactionFactory: Your " "Hibernate properties should *not* include a 'hibernate.transaction.factory_class' property!"); } if (logger.isDebugEnabled()) { logger.debug( "Not preparing JDBC Connection of Hibernate Session [" SessionFactoryUtils.toString(session) "]"); } } if (definition.isReadOnly() && txObject.isNewSession()) { // Just set to NEVER in case of a new Session for this transaction. session.setFlushMode(FlushMode.MANUAL); } if (!definition.isReadOnly() && !txObject.isNewSession()) { // We need AUTO or COMMIT for a non-read-only transaction. FlushMode flushMode = session.getFlushMode(); if (flushMode.lessThan(FlushMode.COMMIT)) { session.setFlushMode(FlushMode.AUTO); txObject.getSessionHolder().setPreviousFlushMode(flushMode); } } Transaction hibTx; // Register transaction timeout. int timeout = determineTimeout(definition); if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) { // Use Hibernate's own transaction timeout mechanism on Hibernate 3.1 // Applies to all statements, also to inserts, updates and deletes! hibTx = session.getTransaction(); hibTx.setTimeout(timeout); hibTx.begin(); } else { // Open a plain Hibernate transaction without specified timeout. hibTx = session.beginTransaction(); } // Add the Hibernate transaction to the session holder. txObject.getSessionHolder().setTransaction(hibTx); // Register the Hibernate Session's JDBC Connection for the DataSource, if set. if (getDataSource() != null) { Connection con = session.connection(); ConnectionHolder conHolder = new ConnectionHolder(con); if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) { conHolder.setTimeoutInSeconds(timeout); } if (logger.isDebugEnabled()) { logger.debug("Exposing Hibernate transaction as JDBC transaction [" con "]"); } TransactionSynchronizationManager.bindResource(getDataSource(), conHolder); txObject.setConnectionHolder(conHolder); } // Bind the session holder to the thread. if (txObject.isNewSessionHolder()) { TransactionSynchronizationManager.bindResource(getSessionFactory(), txObject.getSessionHolder()); } txObject.getSessionHolder().setSynchronizedWithTransaction(true); } catch (Exception ex) { if (txObject.isNewSession()) { try { if (session.getTransaction().isActive()) { session.getTransaction().rollback(); } } catch (Throwable ex2) { logger.debug("Could not rollback Session after failed transaction begin", ex); } finally { SessionFactoryUtils.closeSession(session); } } throw new CannotCreateTransactionException("Could not open Hibernate Session for transaction", ex); } }
結論:
Session的使用維護上有幾種:
1.getCurrentSession() : 獲得當前會話中的session,該session有容器自行維護管理,Spring可以代理事務。
2.this.getSession() : 從當前的執行中獲得或create 一個hibernate的session,自己關閉,釋放連接資源。
3.openSession(); 調用函數自行create一個數據庫的連接,並將其打開,在使用Spring操作非查詢語句的請況下,Spring的transaction 對該session對像不起到Transaction 管理的作用,所以該session對象應自己關閉,釋放連接資源。
當使用Spring 去管理Hibernate Session的時候,DAO Extends HibernateDaoSupport 時,session的取得我們並不在乎,如果需要取得session做處理時,HibernateTemplate提供HibernateCallback,就是为了满足使用了HibernateTemplate的情况下,仍然需要直接訪問Session的狀況。
個人認為Service Layer,DAO Layer所做的事應該單一化, Service 針對Method 做Transaction , 一個Method 代表的應該是一個完整的Transaction動作,要Reference其他Service所做的事時應該在Controll 呼叫2個不同的Service method。而DAO 所做的事應該是單純的DB動作。
Notes:
ServiceA 有個method 叫 find(id) , @Transactional(propagation=Propagation.REQUIRED)
ServiceB 也有個method 叫 find(id) , @Transactional(propagation=Propagation.REQUIRED)
然後在ServiceA 的find(id) 中 call ServiceB 的find(id)
Spring Log如下
Ts-[] ,15:24:47 [DEBUG] AbstractPlatformTransactionManager.java:370 - Creating new transaction with name [com.service.UserServiceImpl.find]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT Ts-[] ,15:24:47 [DEBUG] HibernateTransactionManager.java:493 - Opened new Session [org.hibernate.impl.SessionImpl@940f82] for Hibernate transaction Ts-[] ,15:24:47 [DEBUG] HibernateTransactionManager.java:504 - Preparing JDBC Connection of Hibernate Session [org.hibernate.impl.SessionImpl@940f82] Ts-[] ,15:24:47 [DEBUG] DriverManagerDataSource.java:162 - Creating new JDBC DriverManager Connection to [jdbc:mysql://127.0.0.1:3306/test] Ts-[] ,15:24:47 [DEBUG] HibernateTransactionManager.java:569 - Exposing Hibernate transaction as JDBC transaction [com.mysql.jdbc.JDBC4Connection@92668c] 15:24:47 [DEBUG] HibernateTransactionManager.java:437 - Found thread-bound Session [org.hibernate.impl.SessionImpl@940f82] for Hibernate transaction Ts-[] ,15:24:47 [DEBUG] AbstractPlatformTransactionManager.java:468 - Participating in existing transaction [DEBUG] AbstractPlatformTransactionManager.java:729 - Initiating transaction commit Ts-[] ,15:24:47 [DEBUG] HibernateTransactionManager.java:652 - Committing Hibernate transaction on Session [org.hibernate.impl.SessionImpl@940f82] Ts-[] ,15:24:47 [DEBUG] HibernateTransactionManager.java:734 - Closing Hibernate Session [org.hibernate.impl.SessionImpl@940f82] after transaction Ts-[] ,15:24:47 [DEBUG] SessionFactoryUtils.java:784 - Closing Hibernate Session設為 REQUIRED - Support a current transaction, create a new one if none exists.
詳見Spring API doc
See also:
Hibernate Documentation
Spring Documentation
Tutorial:Create Spring 3 MVC Hibernate 3 Example
沒有留言:
張貼留言