最近对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();是不对的,这句代码是完全多余的。
|