最近對Hibernate的ThreadLocal Session模式有點興趣。于是根據曹曉鋼翻譯的Hibernate Reference做了個小測驗,結果發現了一個小bug。 代碼很簡單,都是利用Hibernate Reference中現成的代碼。 首先是一個輔助的得到線程安全的session的HibernateUtil類,
public class HibernateUtil { public static final SessionFactory sessionFactory; static{ try { sessionFactory = new Configuration().configure().buildSessionFactory(); } catch(Throwable ex){ throw new ExceptionInInitializerError(ex); } }
public static final ThreadLocal session = new ThreadLocal(); public static Session currentSession() { Session s = (Session) session.get(); if (s==null ) { s = sessionFactory.getCurrentSession(); session.set(s); } return s; } public static void closeSession() { Session s = (Session) session.get(); if (s!=null) s.close(); session.set(null); } public static SessionFactory getSessionFactory() { return sessionFactory; } } 然后是一個測試插入數據的代碼。也很簡單,也是仿Hibernate Reference上面的代碼。 public class InsertUser { public static void main(String[] args) { Session session = HibernateUtil.currentSession(); Transaction tx= session.beginTransaction(); TUser user = new TUser(); user.setName("Emma"); session.save(user); tx.commit(); HibernateUtil.closeSession(); } }
就這么簡單一個程序,運行到最后,出現一個錯誤。
org.hibernate.SessionException: Session was already closed at org.hibernate.impl.SessionImpl.close(SessionImpl.java:270) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source) at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source) at java.lang.reflect.Method.invoke(Unknown Source) at org.hibernate.context.ThreadLocalSessionContext$TransactionProtectionWrapper.invoke(ThreadLocalSessionContext.java:301) at $Proxy0.close(Unknown Source) at Util.HibernateUtil.closeSession(HibernateUtil.java:36) at test.InsertUser.main(InsertUser.java:20) Exception in thread "main"
錯誤出現在 HibernateUtil.closeSession(); 這一行,意思是session已經關閉了,再次關閉它就引起異常了。
不過前面的代碼中只有個tx.commit(); 提交事務 而已,并沒有自動關閉session啊?
于是把DEBUG信息調用出來,發現了以下幾句提示: DEBUG [main] - after transaction completion DEBUG [main] - automatically closing session DEBUG [main] - closing session DEBUG [main] - connection already null in cleanup : no action DEBUG [main] - allowing proxied method [close] to proceed to real session DEBUG [main] - closing session org.hibernate.SessionException: Session was already closed
特別是下面這3句話引起了我的注意,果然是session關閉了,而且是在 事務結束以后自動關閉的。 DEBUG [main] - after transaction completion DEBUG [main] - automatically closing session DEBUG [main] - closing session
那么這個機制是怎么發生的呢?
打開了Hibernate3的源碼,我找到了答案。 首先,根據sessionFactory = new Configuration().configure().buildSessionFactory(); 打開Configuration類的buildSessionFactory()方法,找到sessionFactory的生成語句 return new SessionFactoryImpl( this, mapping, settings, getInitializedEventListeners() ); ,然后找到SessionFactoryImpl的getCurrentSession方法,發現是這么定義的。
public org.hibernate.classic.Session getCurrentSession() throws HibernateException { if ( currentSessionContext == null ) { throw new HibernateException( "No CurrentSessionContext configured!" ); } return currentSessionContext.currentSession(); }
他調用的是一個currentSessionContext的currentSession方法。查找currentSessionContext變量,
currentSessionContext = buildCurrentSessionContext();
,知道了buildCurrentSessionContext方法產生了這個currentSessionContext 對象。
private CurrentSessionContext buildCurrentSessionContext() { String impl = properties.getProperty( Environment.CURRENT_SESSION_CONTEXT_CLASS ); // for backward-compatability if ( impl == null && transactionManager != null ) { impl = "jta"; }
if ( impl == null ) { return null; } else if ( "jta".equals( impl ) ) { return new JTASessionContext( this ); } else if ( "thread".equals( impl ) ) { return new ThreadLocalSessionContext( this ); } else { try { Class implClass = ReflectHelper.classForName( impl ); return ( CurrentSessionContext ) implClass .getConstructor( new Class[] { SessionFactoryImplementor.class } ) .newInstance( new Object[] { this } ); } catch( Throwable t ) { log.error( "Unable to construct current session context [" + impl + "]", t ); return null; } } }
這個方法就是用來判斷使用JTA管理這個SessionContext還是用ThreadLocal來管理SessionContext的。 在我們這里是用 ThreadLocal 來管理的,于是找到了currentSessionContext 的實現類是 ThreadLocalSessionContext。
找到該類的currentSession方法
public final Session currentSession() throws HibernateException { Session current = existingSession( factory ); if (current == null) { current = buildOrObtainSession(); // register a cleanup synch current.getTransaction().registerSynchronization( buildCleanupSynch() ); // wrap the session in the transaction-protection proxy if ( needsWrapping( current ) ) { current = wrap( current ); } // then bind it doBind( current, factory ); } return current; }
然后跟蹤到 buildOrObtainSession(),就是這里,打開了session。
protected Session buildOrObtainSession() { return factory.openSession( null, isAutoFlushEnabled(), isAutoCloseEnabled(), getConnectionReleaseMode() ); } 注意第三個參數:isAutoCloseEnabled 打開Session這個接口,看到 openSession方法中這個參數是如下描述的: * @param autoCloseSessionEnabled Should the session be auto-closed after * transaction completion?
,就是說session是否應該在事務提交后自動關閉。
然后打開 ThreadLocalSessionContext 的isAutoCloseEnabled()方法。
/** * Mainly for subclass usage. This impl always returns true. * * @return Whether or not the the session should be closed by transaction completion. */ protected boolean isAutoCloseEnabled() { return true; } 看到如下提示:Whether or not the the session should be closed by transaction completion ,即無論如何session應該在事務完成后關閉。
答案就在這里,就是說在ThreadLocal Session模式下面,只要提交了事務,那么session就自動關閉了,因此我參照Hibernate Refernece上面的代碼寫的在事務關閉以后再調用HibernateUtil.closeSession();是不對的,這句代碼是完全多余的。
|