"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
沒有留言:
張貼留言