论坛首页 Java企业应用论坛

Spring集成Hibernate之Session管理

浏览 18973 次
精华帖 (1) :: 良好帖 (7) :: 新手帖 (0) :: 隐藏帖 (1)
作者 正文
   发表时间:2011-08-14   最后修改:2011-08-14
   


用过Hibernate的人都知道Hibernate最原始的使用Session方式(异常忽略):

 

获取SessionFactory

打开Session

打开事务(可选)

执行操作

关闭事务(可选)

关闭Session

 

 

    当然还有另外一个方法getCurrentSession() 这个方法就是通过SessionContext来减少Session创建的。比如常用的ThreadLocalSessionContext:

 

Session current = existingSession( factory );
if (current == null) {
	current = buildOrObtainSession();
	current.getTransaction().registerSynchronization( buildCleanupSynch() );
	if ( needsWrapping( current ) ) {
		current = wrap( current );
	}
	doBind( current, factory );
}
return current;

 

        currentSession()内部先从ThreadLocal中获取Session。若不为null直接返回,若为null则openSession(...)一个Session并把这个Session对象绑定在ThreadLocal上。它就是通过在ThreadLocal中注册绑定Session来确保线程中最多只有一个Session对象。

 

 

上面的地球人都知道

==============================================================================

 

Spring提供许多Template对各种底层ORM等进行集成,如JdbcTemplate、HibernateTemplate、JpaTemplate等同时也提供了相应的Dao模板类,如JdbcDaoSupport、HibernateDaoSupport、JpaDaoSupport等

既然说Spring对Hibernate的集成,就得看HibernateTemplate和HibernateDaoSupport这两个类。

     使用HibernateDaoSupport进行数据操作时常用两种方式访问Session:getSession()和HibernateCallback。

注意:使用HibernateDaoSupport时候、如果再通过SessionFactory进行getCurrentSession()获取Session的话就有可能出现问题了。因为Spring的Bean工厂把Hibernate的SessionFactory动了点小手脚 ~。就是前面说到的ThreadLocalSessionContext被Spring替换为 SpringSessionContext !

这个SpringSessionContext的currentSession()方法核心如下:
    return (org.hibernate.classic.Session) SessionFactoryUtils.doGetSession(this.sessionFactory, false );
    // 这个false硬性规定doGetSession如果在线程中获取不到Session不自动创建Session

 

SessionFactoryUtils#doGetSession()是通过TransactionSynchronizationManager来获取Session资源的,doGetSession()会通过 TransactionSynchronizationManager访问线程资源、但是整个操作中没有打开事务的话、此方法会抛出异常:
         org.hibernate.HibernateException: No Hibernate Session bound to thread, and configuration does not allow creation of non-transactional one here

 

    这个异常就是出现在SessionFactoryUtils#doGetSession()方法的最后:

if (!allowCreate && !isSessionTransactional(session, sessionFactory)) {
	closeSession(session);
	throw new IllegalStateException("No Hibernate Session bound to thread, " +
		"and configuration does not allow creation of non-transactional one here");
}
 

 

 

        getSession和getCurrentSession实现过程差不多,但是精简了SessionContext那一段。并且可以设置allowCreate,如果设置为false,此方法应该和getCurrentSession同样效果。

 

 

=========================================================================

 

 

了解了这些之后、我进行了一个HibernateTemplate和getSession之间的测试,主要就是测试

单线程下多次重复请求创建Session与事务之间的关系、HibernateTemplate和getSession()两者之间在功能实现和效率上有什么样的不同。(多线程下卡的要死、不测试了)

 

Dao层: testTS()方法——临时创建用于测试的方法
            通过HibernateTemplate和直接getSession等方法获取Session并保存。          
            打印已经保存的Session的状态。
            打印已经保存的Session之间是否相同。
            打印SessionFactory的状态记录。
Service层: testTS()方法——临时创建用于测试的方法
        内部代码为执行三次请求:
         getDao().testTS();
         getDao().testTS();
         getDao().testTS();

 

 

第一次测试

    Service层的testTS(e)方法不打开事务。   
    单线程。

    使用HibernateTemplate。  

结论:

Service层没有打开事务,但是每次操作CURD操作时,HibernateTemplate都会自动创建

Transactional。同一线程获取到的前后两个Session之间互不相同。

  --------------Closed Transactional && Single-Thread--------------
  Last Session Status : false    //每次Session使用完自动关闭
  This Session Status : false
  Compared Session : false  

 

      测试数据扩大100倍并记时:
      REQUEST[100]
      Opened Session Count :300
      Transaction Count :600   //我没有声明打开事务啊!为什么它还为我打开事务
      Connection Count :300
      ---------------------------------------
      TIME : 1719

 

第二次测试

    Service层的testTS(e)方法打开事务。   
    单线程。
    使用HibernateTemplate。

结论:

在事务边界内获取的Session由Spring管理、不必手动关闭

虽然打开了事务,但是同一线程下同一事务边界内前后获取的两个Session仍然不同! 这是为什么???

后面会继续剖析HibernateTemplate源码,给出解释

  --------------Open Transactional && Single-Thread--------------
  Last Session Status : Open    //每次Session使用完之后未关闭
  This Session Status : Open
  Compared Session : false    //同一线程同一个事务前后获取的两个Session还不同!!!!!为什么?????

   

        测试数据扩大100倍并记时:
        REQUEST[100]
        Opened Session Count :100
        Transaction Count :200
        Connection Count :100
        ---------------------------------------
        TIME : 1719

 

第三次测试

    Service层的testTS(e)方法不打开事务。   
    单线程。
    使用getSession()。

可以看到getSession()由于缺少Spring的支持,在无事务环境下。

它为每个CURD请求创建了一个Session。并且更让人震惊的是,它好像为每个Session都创建了新

Connection 。这些Session使用之后,它也并不关闭。

  --------------Closed Transactional && Single-Thread--------------
  Last Session Status : Open
  This Session Status : Open
  Compared Session : false

   

     测试数据扩大100倍并记时,令人极其震惊且极其坑爹的一幕出现了!我亲眼看到程序以一种 肉眼可见的速度

慢腾腾的执行一次又一次的循环,它竟然执行了一分钟!!!:
        REQUEST[100]
        Opened Session Count :300
        Transaction Count :0
        Connection Count :300
        ---------------------------------------
        TIME : 53844

 

第四次测试

    Service层的testTS(e)方法打开事务。   
    单线程。

    使用getSession() 。

这个最容易让人理解、由于同一线程且在同一事务边界内,前后两个Session相同。并且也不会重复创建

Connection。

  --------------Open Transactional && Single-Thread--------------
  Last Session Status : Open    //每次Session使用完之后未关闭
  This Session Status : Open
  Compared Session : True    //同一线程前后获取的两个Session相同

         测试数据扩大100倍并记时:
         REQUEST[100]
         Opened Session Count :100
         Transaction Count :200
         Connection Count :100
         ---------------------------------------
         TIME : 1204

 

 

总结:

      getSession()在事务边界内会通过TransactionSynchronizationManager获取Session资源,同一线程内它不会重复创建Connection , 那些获取到的Session()不需要手动关闭。但是在无声明式事务环境下,它就会表现出极其坑爹的状况,它反复获取Connection,也不关闭Session。非常消耗资源

 

      HibernateTemplate有一个安全且高效的Session环境,它的CRUD都是位于HibernateCallback内部,如果它的CRUD并没有位于事务之中,它会自己创建一个事务(Spring集成Hibernate时,所有的资源都是由TransactionSynchronizationManager管理的 )。同一个线程中它只需要一个Connection。Session也会在事务边界处自动关闭,程序员不需要关注此事(这个事务边界可能是@Transactional显式声明的也可能是HibernateTemplate#doExecute隐式声明的)。

 

     在同一个事务边界内,两个HibernateTemplate操作内部的Session互也不相同这个问题可以在HibernateTemplate源码中找到答案:

protected <T> T doExecute(HibernateCallback<T> action, boolean enforceNewSession, boolean enforceNativeSession)
		throws DataAccessException {
	......
	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;
	......
}
通过
Session sessionToExpose =
				(enforceNativeSession || isExposeNativeSession() ? 
								session : createSessionProxy(session));
红色部分可以看到HibernateTemplate中获取的Session不是原生的,而是代理的。那个代理类是一个比较简单的内部类,源代码位于HibernateTemplate类文件最下部分。每次HibernateTemplate#doExecute执行时除非声明不使用代理类,Spring都会使用线程中的Session资源来创建代理。但是这个代理类的创建对性能的影响微不足道了,Spring这样做肯定有它的道理。

 

 

 

各位英雄好汉,不要乱投新手帖、隐藏贴奥。

咱琢磨了好几天的东西,你们高手们就不要否决了

   发表时间:2011-08-14  
ItEye的编辑功能真愁人、 弄了一个小时还是这熊样。。。

为什么一个通过
SessionFactory.getStatistics().getTransactionCount()获取的Transaction数量
是通过
SessionFactory.getStatistics().getSessionOpenCount()获取的Session数量
的两倍呢????

难道一个Session都需要Spring创建两个Transaction???
0 请登录后投票
   发表时间:2011-08-15  
哇,楼主你好棒耶……
0 请登录后投票
   发表时间:2011-08-15  

用过Hibernate的人都知道Hibernate最原始的使用Session方式(异常忽略):

 

获取SessionFactory

打开Session

打开事务(可选)

执行操作

关闭事务(可选)

关闭Session

 

 

    当然还有另外一个方法getCurrentSession() 这个方法就是通过SessionContext来减少Session创建的。比如常用的ThreadLocalSessionContext:

 

Session current = existingSession( factory );
if (current == null) {
	current = buildOrObtainSession();
	current.getTransaction().registerSynchronization( buildCleanupSynch() );
	if ( needsWrapping( current ) ) {
		current = wrap( current );
	}
	doBind( current, factory );
}
return current;

 

        currentSession()内部先从ThreadLocal中获取Session。若不为null直接返回,若为null则openSession(...)一个Session并把这个Session对象绑定在ThreadLocal上。它就是通过在ThreadLocal中注册绑定Session来确保线程中最多只有一个Session对象。

 

 

上面的地球人都知道

==============================================================================

 

Spring提供许多Template对各种底层ORM等进行集成,如JdbcTemplate、HibernateTemplate、JpaTemplate等同时也提供了相应的Dao模板类,如JdbcDaoSupport、HibernateDaoSupport、JpaDaoSupport等

既然说Spring对Hibernate的集成,就得看HibernateTemplate和HibernateDaoSupport这两个类。

     使用HibernateDaoSupport进行数据操作时常用两种方式访问Session:getSession()和HibernateCallback。

注意:使用HibernateDaoSupport时候、如果再通过SessionFactory进行getCurrentSession()获取Session的话就有可能出现问题了。因为Spring的Bean工厂把Hibernate的SessionFactory动了点小手脚 ~。就是前面说到的ThreadLocalSessionContext被Spring替换为 SpringSessionContext !

这个SpringSessionContext的currentSession()方法核心如下:
    return (org.hibernate.classic.Session) SessionFactoryUtils.doGetSession(this.sessionFactory, false );
    // 这个false硬性规定doGetSession如果在线程中获取不到Session不自动创建Session

 

SessionFactoryUtils#doGetSession()是通过TransactionSynchronizationManager来获取Session资源的,doGetSession()会通过 TransactionSynchronizationManager访问线程资源、但是整个操作中没有打开事务的话、此方法会抛出异常:
         org.hibernate.HibernateException: No Hibernate Session bound to thread, and configuration does not allow creation of non-transactional one here

 

    这个异常就是出现在SessionFactoryUtils#doGetSession()方法的最后:

if (!allowCreate && !isSessionTransactional(session, sessionFactory)) {
	closeSession(session);
	throw new IllegalStateException("No Hibernate Session bound to thread, " +
		"and configuration does not allow creation of non-transactional one here");
}
 

 

 

        getSession和getCurrentSession实现过程差不多,但是精简了SessionContext那一段。并且可以设置allowCreate,如果设置为false,此方法应该和getCurrentSession同样效果。

 

 

=========================================================================

 

 

了解了这些之后、我进行了一个HibernateTemplate和getSession之间的测试,主要就是测试

单线程下多次重复请求创建Session与事务之间的关系、HibernateTemplate和getSession()两者之间在功能实现和效率上有什么样的不同。(多线程下卡的要死、不测试了)

 

Dao层: testTS()方法——临时创建用于测试的方法
            通过HibernateTemplate和直接getSession等方法获取Session并保存。          
            打印已经保存的Session的状态。
            打印已经保存的Session之间是否相同。
            打印SessionFactory的状态记录。
Service层: testTS()方法——临时创建用于测试的方法
        内部代码为执行三次请求:
         getDao().testTS();
         getDao().testTS();
         getDao().testTS();

 

 

第一次测试

    Service层的testTS(e)方法不打开事务。   
    单线程。

    使用HibernateTemplate。  

结论:

Service层没有打开事务,但是每次操作CURD操作时,HibernateTemplate都会自动创建

Transactional。同一线程获取到的前后两个Session之间互不相同。

  --------------Closed Transactional && Single-Thread--------------
  Last Session Status : false    //每次Session使用完自动关闭
  This Session Status : false
  Compared Session : false  

 

      测试数据扩大100倍并记时:
      REQUEST[100]
      Opened Session Count :300
      Transaction Count :600   //我没有声明打开事务啊!为什么它还为我打开事务
      Connection Count :300
      ---------------------------------------
      TIME : 1719

 

第二次测试

    Service层的testTS(e)方法打开事务。   
    单线程。
    使用HibernateTemplate。

结论:

在事务边界内获取的Session由Spring管理、不必手动关闭

虽然打开了事务,但是同一线程下同一事务边界内前后获取的两个Session仍然不同! 这是为什么???

后面会继续剖析HibernateTemplate源码,给出解释

  --------------Open Transactional && Single-Thread--------------
  Last Session Status : Open    //每次Session使用完之后未关闭
  This Session Status : Open
  Compared Session : false    //同一线程同一个事务前后获取的两个Session还不同!!!!!为什么?????

   

        测试数据扩大100倍并记时:
        REQUEST[100]
        Opened Session Count :100
        Transaction Count :200
        Connection Count :100
        ---------------------------------------
        TIME : 1719

 

第三次测试

    Service层的testTS(e)方法不打开事务。   
    单线程。
    使用getSession()。

可以看到getSession()由于缺少Spring的支持,在无事务环境下。

它为每个CURD请求创建了一个Session。并且更让人震惊的是,它好像为每个Session都创建了新

Connection 。这些Session使用之后,它也并不关闭。

  --------------Closed Transactional && Single-Thread--------------
  Last Session Status : Open
  This Session Status : Open
  Compared Session : false

   

     测试数据扩大100倍并记时,令人极其震惊且极其坑爹的一幕出现了!我亲眼看到程序以一种 肉眼可见的速度

慢腾腾的执行一次又一次的循环,它竟然执行了一分钟!!!:
        REQUEST[100]
        Opened Session Count :300
        Transaction Count :0
        Connection Count :300
        ---------------------------------------
        TIME : 53844

 

第四次测试

    Service层的testTS(e)方法打开事务。   
    单线程。

    使用getSession() 。

这个最容易让人理解、由于同一线程且在同一事务边界内,前后两个Session相同。并且也不会重复创建

Connection。

  --------------Open Transactional && Single-Thread--------------
  Last Session Status : Open    //每次Session使用完之后未关闭
  This Session Status : Open
  Compared Session : True    //同一线程前后获取的两个Session相同

         测试数据扩大100倍并记时:
         REQUEST[100]
         Opened Session Count :100
         Transaction Count :200
         Connection Count :100
         ---------------------------------------
         TIME : 1204

 

 

总结:

      getSession()在事务边界内会通过TransactionSynchronizationManager获取Session资源,同一线程内它不会重复创建Connection , 那些获取到的Session()不需要手动关闭。但是在无声明式事务环境下,它就会表现出极其坑爹的状况,它反复获取Connection,也不关闭Session。非常消耗资源

 

      HibernateTemplate有一个安全且高效的Session环境,它的CRUD都是位于HibernateCallback内部,如果它的CRUD并没有位于事务之中,它会自己创建一个事务(Spring集成Hibernate时,所有的资源都是由TransactionSynchronizationManager管理的 )。同一个线程中它只需要一个Connection。Session也会在事务边界处自动关闭,程序员不需要关注此事(这个事务边界可能是@Transactional显式声明的也可能是HibernateTemplate#doExecute隐式声明的)。

 

     在同一个事务边界内,两个HibernateTemplate操作内部的Session互也不相同这个问题可以在HibernateTemplate源码中找到答案:

protected <T> T doExecute(HibernateCallback<T> action, boolean enforceNewSession, boolean enforceNativeSession)
		throws DataAccessException {
	......
	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;
	......
}
通过
Session sessionToExpose =
				(enforceNativeSession || isExposeNativeSession() ? 
								session : createSessionProxy(session));
红色部分可以看到HibernateTemplate中获取的Session不是原生的,而是代理的。那个代理类是一个比较简单的内部类,源代码位于HibernateTemplate类文件最下部分。每次HibernateTemplate#doExecute执行时除非声明不使用代理类,Spring都会使用线程中的Session资源来创建代理。但是这个代理类的创建对性能的影响微不足道了,Spring这样做肯定有它的道理。

 

 

0 请登录后投票
   发表时间:2011-08-16  
我擦,好帖木人回复,戳帖全都来骂。无语了,先顶一个再说。
0 请登录后投票
   发表时间:2011-08-16  
http://www.iteye.com/topic/733971
0 请登录后投票
   发表时间:2011-08-16  
楼主分析的不错,如果用openinsessionview可以getSession也可以自动关闭,前提是配置事务
0 请登录后投票
   发表时间:2011-08-16  
受益了.. 
0 请登录后投票
   发表时间:2011-08-16  
rentianchou 写道
楼主分析的不错,如果用openinsessionview可以getSession也可以自动关闭,前提是配置事务


那样的话,要为每个线程都打开Session的啊

真正操作时候并不需要的, 比如用户注册账号, 经常是填写多次才能合法注册。

也就是多个Request可能有一个需要打开Session, 其他的都不需要打开Session,否则就是资源浪费
0 请登录后投票
   发表时间:2011-08-16  
写的挺好!!
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics