论坛首页 Java企业应用论坛

OpenSessionInView详解

浏览 87246 次
该帖已经被评为精华帖
作者 正文
   发表时间:2006-11-01  
OpenSessionInViewFilter是Spring提供的一个针对Hibernate的一个支持类,其主要意思是在发起一个页面请求时打开Hibernate的Session,一直保持这个Session,直到这个请求结束,具体是通过一个Filter来实现的。

由于Hibernate引入了Lazy Load特性,使得脱离Hibernate的Session周期的对象如果再想通过getter方法取到其关联对象的值,Hibernate会抛出一个LazyLoad的Exception。所以为了解决这个问题,Spring引入了这个Filter,使得Hibernate的Session的生命周期变长。

首先分析一下它的源码,可以发现,它所实现的功能其实比较简单:
SessionFactory sessionFactory = lookupSessionFactory(request);
Session session = null;
boolean participate = false;

if (isSingleSession()) {
	// single session mode
	if (TransactionSynchronizationManager.hasResource(sessionFactory)) {
	// Do not modify the Session: just set the participate flag.
	participate = true;
       }	else {
	logger.debug("Opening single Hibernate Session in OpenSessionInViewFilter");
	session = getSession(sessionFactory);
	TransactionSynchronizationManager.bindResource(sessionFactory, new SessionHolder(session));
	}
} else {
	// deferred close mode
	if (SessionFactoryUtils.isDeferredCloseActive(sessionFactory)) {
// Do not modify deferred close: just set the participate flag.
	participate = true;
    } else {
	SessionFactoryUtils.initDeferredClose(sessionFactory);
    }
}

try {
	filterChain.doFilter(request, response);
} finally {
	if (!participate) {
           	if (isSingleSession()) {
	         	// single session mode
		TransactionSynchronizationManager.unbindResource(sessionFactory);
		logger.debug("Closing single Hibernate Session in OpenSessionInViewFilter");
		closeSession(session, sessionFactory);
	}else {
		// deferred close mode
		SessionFactoryUtils.processDeferredClose(sessionFactory);
	}
}
}

在上述代码中,首先获得SessionFactory,然后通过SessionFactory获得一个Session。然后执行真正的Action代码,最后根据情况将Hibernate的Session进行关闭。整个思路比较清晰。

注意,在这里有个2个Tips:
1)通过getSession()获得的这个Session做了一次
session.setFlushMode(FlushMode.NEVER); 有关FlushMode可以参考一下这篇文章。http://www2.matrix.org.cn/resource/article/2006-10-08/Hibernate+FlushMode+NEVER_312bca85-5699-11db-91a0-d98dff0aec60.html
2)Spring对拿到的Session做了一次绑定到当前线程的做法,使得这个Session是线程安全的。

从上述代码其实可以得到一些对我们的开发有帮助的结论:
1)如果使用了OpenSessionInView模式,那么Spring会帮助你管理Session的开和关,从而你在你的DAO中通过HibernateDaoSupport拿到的getSession()方法,都是绑定到当前线程的线程安全的Session,即拿即用,最后会由Filter统一关闭。
2)由于拿到的Hibernate的Session被设置了session.setFlushMode(FlushMode.NEVER); 所以,除非你直接调用session.flush(),否则Hibernate session无论何时也不会flush任何的状态变化到数据库。因此,数据库事务的配置非常重要。(我们知道,在调用org.hibernate.Transaction.commit()的时候会触发session.flush())我曾经见过很多人在使用OpenSessionInView模式时,都因为没有正确配置事务,导致了底层会抛出有关FlushMode.NEVER的异常。

OpenSessionInView这个模式使用比较简单,也成为了大家在Web开发中经常使用的方法,不过它有时候会带来一些意想不到的问题,这也是在开发中需要注意的。
1. 在Struts+Spring+Hibernate环境中,由于配置的问题导致的模式失效
这个问题以前论坛曾经讨论过,可以参考一下下面这个帖子:
http://www.iteye.com/topic/15057

2. OpenSessionInView的效率问题
这个问题也有人在论坛提出过,Robbin曾经做过具体的测试,可以具体参考一下下面这个帖子:
http://www.iteye.com/topic/17501

3. 由于使用了OpenSessionInView模式后造成了内存和数据库连接问题
这个问题是我在生产环境中碰到的一个问题。由于使用了OpenSessionInView模式,Session的生命周期变得非常长。虽然解决了Lazy Load的问题,但是带来的问题就是Hibernate的一级缓存,也就是Session级别的缓存的生命周期会变得非常长,那么如果你在你的Service层做大批量的数据操作时,其实这些数据会在缓存中保留一份,这是非常耗费内存的。还有一个数据库连接的问题,存在的原因在于由于数据库的Connection是和Session绑在一起的,所以,Connection也会得不到及时的释放。因而当系统出现业务非常繁忙,而计算量又非常大的时候,往往数据连接池的连接数会不够。这个问题我至今非常头痛,因为有很多客户对数据连接池的数量会有限制,不会给你无限制的增加下去。

4. 使用了OpenSessionInView模式以后取数据的事务问题
在使用了OpenSessionInView以后,其实事务的生命周期比Session的生命周期来得短,就以为着,其实有相当一部分的查询是不被纳入到事务的范围内的,此时是否会读到脏数据?这个问题我至今不敢确认,有经验的朋友请指教一下。

最后提一下OpenSessionInView模式的一些替代方案,可以使用OpenSessionInViewInterceptor来代替这个Filter,此时可以使用Spring的AOP配置,将这个Interceptor配置到你所需要的层次上去。另外就是只能使用最古老的Hibernate.initialize()方法进行初始化了。
   发表时间:2006-11-01  
downpour 写道

2)由于拿到的Hibernate的Session被设置了session.setFlushMode(FlushMode.NEVER); 所以,除非你直接调用session.flush(),否则Hibernate session无论何时也不会flush任何的状态变化到数据库。因此,数据库事务的配置非常重要。(我们知道,在调用org.hibernate.Transaction.commit()的时候会触发session.flush())我曾经见过很多人在使用OpenSessionInView模式时,都因为没有正确配置事务,导致了底层会抛出有关FlushMode.NEVER的异常。


这点我详细解释一下, HibernteTransactionManager 中会根据事务设置改变 session 的刷新方式, 具体代码参见 HibernteTransactionManager  411 行 (Spring 1.2.8)

			if (definition.isReadOnly() && txObject.isNewSessionHolder()) {
				// Just set to NEVER in case of a new Session for this transaction.
				session.setFlushMode(FlushMode.NEVER);
			}

			if (!definition.isReadOnly() && !txObject.isNewSessionHolder()) {
				// We need AUTO or COMMIT for a non-read-only transaction.
				FlushMode flushMode = session.getFlushMode();
				if (FlushMode.NEVER.equals(flushMode)) {
					session.setFlushMode(FlushMode.AUTO);
					txObject.getSessionHolder().setPreviousFlushMode(flushMode);
				}
			}



具体的含义, 从代码看已经很清晰了, 不用再赘述

downpour 写道

4. 使用了OpenSessionInView模式以后取数据的事务问题
在使用了OpenSessionInView以后,其实事务的生命周期比Session的生命周期来得短,就以为着,其实有相当一部分的查询是不被纳入到事务的范围内的,此时是否会读到脏数据?这个问题我至今不敢确认,有经验的朋友请指教一下。


这个问题根据我的理解, 应该不会存在脏数据的问题, 从 Hibernate 的说明上可以得知, hibernate 保证在同一个 session 中调用 load, get 或 query 时, 得到的是同一个对象, 因此事务结束后得到的对象是已经被更新过的对象
0 请登录后投票
   发表时间:2006-11-01  
前些日子发现了一个连 OpenSessionInView 都解决不了的 Lazy 问题, 甚是郁闷, 原因是这样的

架构中使用 OpenSessionInView 和 HibernateTransactionManager, 本来相安无事, 结果因为我的异常处理让 
OpenSessionInView 失效了,  异常处理方式是出现异常后继续返回到异常前的页面,  HibernateTransactionManager 的处理使得 OpenSessionInView 实效
	protected void doRollback(DefaultTransactionStatus status) {
                
               ... 以上省略

		finally {
			if (!txObject.isNewSessionHolder()) {
				// Clear all pending inserts/updates/deletes in the Session.
				// Necessary for pre-bound Sessions, to avoid inconsistent state.
				txObject.getSessionHolder().getSession().clear();
			}
		}
	}


它这一 clear, 把 session  的状态全清楚了, 包括 lazy load 的对象, 这个问题有时间我准备发到 spring 的 bug list 上去
0 请登录后投票
   发表时间:2006-11-09  
另外今天发现,OpenSessionInView这个Filter,其实问题还是多多啊。

protected Session getSession(SessionFactory sessionFactory) throws DataAccessResourceFailureException {
	Session session = SessionFactoryUtils.getSession(sessionFactory, true);
	session.setFlushMode(FlushMode.NEVER);
	return session;
}


这个地方它使用了SessionFactoryUtils.getSession(sessionFactory, true);的方式去拿到Session,这样存在的一个很重大的问题,通过这个方法拿到的Session,是不带任何的interceptor的。看看另外一个OpenSessionInViewInterceptor的实现,它去取Session的时候,却是以这样的方式:

Session session = SessionFactoryUtils.getSession(getSessionFactory(), getEntityInterceptor(), getJdbcExceptionTranslator());


所以如果要使用Hibernate自带的那些Interceptor的同学们也注意了,使用OpenSessioniInViewFilter的话,那些Interceptor其实是用不上的。
0 请登录后投票
   发表时间:2006-11-10  
对ORM来说这是个难以避免的问题. 类似的, TOB虽然也用关系数据库作为物理存储, 但是自己实现了内存对象级别的事务管理, 这样就只有在检索和提交事务的时候才需要从连接池里去一个connection来用, 之后会立即释放. Session时间再长, 也只是阻塞其他线程对已读或已写的对象的并发修改, 而不会占用关系数据库的连接资源.

而且在对象级别上TOB的事务也是很轻量的, 用TOB的网站可以给所有request加一个filter, 执行完具体逻辑以后根据是否有exception, rollback或者commit.
0 请登录后投票
   发表时间:2006-11-10  
楼上的话说得缺乏依据,对于Hibernate来说,只要Session存在,Connection就一直保持。这一点有源码为证。

只是在Transaction提交或者rollback之后,这个数据库连接是不会占用数据库资源的,但是连接依然保持,尤其是采用数据连接池的时候,这个连接是不会被连接池回收的。
0 请登录后投票
   发表时间:2006-11-10  
downpour 写道
楼上的话说得缺乏依据,对于Hibernate来说,只要Session存在,Connection就一直保持。这一点有源码为证。

只是在Transaction提交或者rollback之后,这个数据库连接是不会占用数据库资源的,但是连接依然保持,尤其是采用数据连接池的时候,这个连接是不会被连接池回收的。

不知道你看的是不是hibernat2,在hibernate3中,连接的管理有更灵活的方式。默认是在事务提交后,关闭连接。至于说openSessionInView占用连接,是因为这个时候session是工作在extend模式,所以连接一直保持到session的关闭(但这个连接与事务所用的连接可能是不同的)。另外spring对连接所做的管理是否影响连接的使用就不是很清楚了。
0 请登录后投票
   发表时间:2006-11-10  
Feiing 写道

它这一 clear, 把 session  的状态全清楚了, 包括 lazy load 的对象, 这个问题有时间我准备发到 spring 的 bug list 上去

不知道你的问题具体是怎样的,但印象中maillist讨论过这个问题,clear()这句是在1.1之前加上的,因为不这样处理出现了不期望的状态修改。java persistence中也按照这样的方式管理context了。
0 请登录后投票
   发表时间:2006-11-11  
downpour 写道
楼上的话说得缺乏依据,对于Hibernate来说,只要Session存在,Connection就一直保持。这一点有源码为证。

只是在Transaction提交或者rollback之后,这个数据库连接是不会占用数据库资源的,但是连接依然保持,尤其是采用数据连接池的时候,这个连接是不会被连接池回收的。

说我的帖子吗? 不太清楚你的意思.

我的意思是说, ORM还是通过关系数据库来控制事务的, 这样ORM的事务期间, 就必须把持住一个RDB的connection不放来保证事务完整.

而TOB这样的对象数据库是自己控制内存对象级别的事务的, 所以可以只在查询和提交TOB事务的时候才需要RDB连接, 这样一个TOB事务持续时间再长, 也不会一直占用RDB连接, 从而不会出现本帖讨论的性能陷阱问题.


其实ORM这个性能陷阱的直接原因是关系数据库的并发连接数限制, 而这个原因的根源是当在关系模型上要维护数据的一致性和事务的独立性时, 如果发生插入或者删除则经常须要在表级别以致整个持久方案级别上隔离(这个可以在一定程度上进行优化,但条件查询条件复杂是非常困难甚至找不到合适的优化方案), 以防止其他连接的事务用被影响了的查询结果去更新其他数据, 进而存在破坏数据一致性的可能. 所以关系数据库的事务维护成本非常之高, 使可能的并发连接/事务数量受到严格限制. 而这个问题的本源又是因为关系数据库所有的数据访问都是基于查询的.

下面我举一个简化的竞拍例子来说明. (例子里的英文比较简单, 我就不翻译成中文了)
0 请登录后投票
   发表时间:2006-11-11  

Simplified Bidding Rule:

*Each item has a hidden maxprice, the first bid reaches this price wins;
*However, the maxprice can be changed whenever, and if lowered to below
any price of existing bids, the bid with highest price wins.


Relational Database

TABLE Item (id AUTO INCREASE, maxprice);

TABLE Bid (id AUTO INCREASE, itemid, price, win DEFAULT FALSE);


TX1:

--found item id of interest: 321

--check item not already sold
 SELECT id FROM Bid WHERE itemid = 321 AND win = TRUE
#IF EXIST id
 COMMIT;
 return;
#ENDIF

--try lower its price by 10.0
 UPDATE Item SET maxprice = maxprice - 10.0 WHERE id = 321;

--find any bid can win agains the new price
 SELECT id FROM Bid WHERE itemid = 321 AND price = MAX(price);

--if bid id found, it wins
#IF EXIST id (assume bid id: 591)
 UPDATE Bid SET win = TRUE WHERE id = 591;
#ENDIF

--done
 COMMIT;


TX2:

--found item id of interest: 321

--see all existing bids, check whether allow new bids (nobody wins)
 SELECT id, price, win FROM Bid WHERE itemid = 321;

--bid a new price: 160.0
 INSERT INTO Bid (itemid, price) VALUES (321, 160.0);

--check if can win the bid, if greater than maxprices, wins
#IF 160.0 >= (SELECT maxprice FROM Item WHERE id = 321)
 UPDATE Bid SET win = TRUE WHERE id = 601; --assume generated bid id: 601
#ENDIF

--done
 COMMIT;




The Object Base

public class Item extends TheObject
{
  @Kin
  protected final Collection<Persistent<? extends Bid>> bids = 
    new ArrayList<Persistent<? extends Bid>>();

  private double maxprice;
  
  public Item(double maxprice)
  {
    this.maxprice = maxprice;
  }
  
  @Reading
  public Collection<Persistent<? extends Bid>> getBids()
  {
    return this.bids;
  }
  
  @Reading
  public double getMaxPrice()
  {
    return this.maxPrice;
  }
  
  @Writing
  public void setMaxPrice(double maxPrice)
  {
    this.maxPrice = maxPrice;
  }
}

public class Bid extends TheRelation
{
  public class ForItem extends Role<Item>
  {
    protected ForItem(Persistent<? extends Item> item)
	{
	  super(item);
	}
  }
  
  @Tie
  protected ForItem item;
  
  private double price;
  
  private boolean win = false;
  
  public Bid(Persistent<? extends Item> item, double price)
  {
    this.item = new ForItem(item);
	this.price = price;
  }
  
  protected Bid()
  {
  }
  
  public ForItem getItem()
  {
    return this.item;
  }
  
  @Reading
  public double getPrice()
  {
    return this.price;
  }
  
  @Reading
  public boolean hasWon()
  {
    return this.win;
  }
  
  @Writing
  public void win()
  {
    this.win = true;
  }
}


TX1:

//found item id of interest: 321
  Persistent<Item> item = tob.get(321);

//check item not already sold
  for(Persistent<? extends Bid> bid : item.o.getBids())
    if(bid.o.hasWon())
    {
      tob.commitAllTransactions();
      return;
    }

//try lower its price by 10.0
 item.o.setMaxPrice(item.o.getMaxPrice() - 10.0);

//find any bid can win agains the new price
	Persistent<? extends Bid> maxBid = null;
	for(Persistent<? extends Bid> bid : getBids())
	{
	  if(bid.o.getPrice() >= this.maxPrice)
	  {
	    if(maxBid != null && maxBid.o.getPrice() > bid.o.getPrice())
		  continue;
      maxBid = bid;
	  }
	}

//if bid id found, it wins
	if(maxBid != null)
	  maxBid.o.win();

//done
 tob.commitAllTransactions();


TX2:

//found item id of interest: 321
  Persistent<Item> item = tob.get(321);

//see all existing bids, check whether allow new bids (nobody wins)
  for(Persistent<? extends Bid> bid : item.o.getBids())
    if(bid.o.hasWon())
    {
      tob.commitAllTransactions();
      return;
    }

//bid a new price: 160.0
  Persistent<Bid> bid = tob.birth(new Bid(item, 160.0));

//check if can win the bid, if greater than maxprices, wins
  if(160.0 >= item.o.getMaxPrice())
    bid.o.win();

//done
 tob.commitAllTransactions();

0 请登录后投票
论坛首页 Java企业应用版

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