1. 程式人生 > >ThreadLocal模式下管理的Session會在事務提交後自動關閉!

ThreadLocal模式下管理的Session會在事務提交後自動關閉!

最近對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應該在事務完成後關閉。

答案就在這裡,就是說在ThreadLocalSession模式下面,只要提交了事務,那麼session就自動關閉了,因此我參照HibernateRefernece上面的程式碼寫的在事務關閉以後再呼叫HibernateUtil.closeSession();是不對的,這句程式碼是完全多餘的。