Open-Session-In-View-Filter

转载:http://www.iteye.com/topic/17501


问:如果singleSession为true,那么Session会从请求开始,一直到页面生成结束、传输给用户才能关闭,这样的话,假如用户打开一个页面要15秒,那么这个Session也要持续15秒,数据库连接也占了15秒。。。 
不知道我说的对不对?
robbin:session持续15秒不意味着数据库连接持续15秒。session只有当真正需要向数据库发送SQL的时候才会申请数据库连接,你打开页面的时候,session还没有申请数据库连接。
问:请问robbin,一旦Hibernate开始向数据库发SQL,申请到叻数据库连接,那么它什么释放连接呢?是执行SQL后马上就释放?
robbin:你说呢?要马上释放连接,还叫什么OpenSessionInView?还怎么lazy?你用OpenSessionInView的目的何在?
问:那这么说来,假设Hibernate在第一秒拿到叻数据库连接,这个连接不还是持续15秒么?
robbin:如果是网络传输速度导致load页面需要15秒,那么时间消耗在网络传输上,本身页面执行时间很短。 
如果不是网络传输速度导致load页面需要15秒,那说明什么?说明这个页面的程序执行了15秒!为什么一个页面程序需要执行这么久呢?说明这个页面的数据库查询非常耗时!(纯内存操作都是ms级别的,如果都10多秒了,瓶颈只可能是数据库操作) 如果数据库查询需要15秒,请问你就算不用OpenSessionInView,你不是一样需要打开数据库连接长达15秒吗?
问:我明白robbin你的意思,我说的情况就是网络传输导致的15秒。本身页面执行的时间,比如freemarker渲染一下模板,这个速度是非常快的,没问题,可是我的疑问就在于如果网络传输狠慢,会不会影响到数据库连接。 


假设WebWork+Hibernate+FreeMarker架构模型是这样的 


Request 
   | 
   |---other filters... 
         | 
         |---OpenSessionInView Filter 
                  | 
                  |-----WebWork Controller 
                          | 
                          |---Action 
                          | 
                          |---FreeMarker Result(对response.getWriter()做process()操作) 
                  | 
                  | 
         |---OpenSessionInView Filter 
         | 
   |---other filters... 
   | 
Request 




这里有两种情况。 


一是页面缓冲区足够大,足够一次性容纳所有的页面,这样渲染页面就会一次性进入缓冲区,然后返回到OpenSessionInView Filter,关闭Session,数据库连接返回池中,一切OK。 


第二种情况我是重点想讨论的,也是我的疑虑所在。那就是假如这个页面比较大,超出叻页面缓冲区的大小,那么渲染页面时,就拿FreeMarker/Velocity这样的模板语言来说,它执行process/merge方法,往servlet的response writer/outputStream里面写东西,写着写着,发现写不动叻,是缓冲区满叻,而这个writer/outputStream,正是服务器同用户之间建立的socket请求,于是底层代码开始先向用户传输一部分页面,传好后,又有叻新的缓冲区,FreeMarker/Velocity的方法又能向缓冲区里写东西叻。而传输页面这个过程,又耗费叻几秒钟的时间,就导致叻数据库连接被占用叻狠长的时间。 


可能我描述的是错误的,希望robbin指正!:)


robbin:我觉得这个问题归根结底就是AppServer究竟会如何实现页面输出。那么必然和具体的应用服务器实现有关系。那么至于每个AppServer究竟会怎样去实现,我就不得而知了。起码大家可以研究一下Tomcat源代码看看tomcat是如何实现的。 


confluence采用的就是Hibernate/Spring/Webwork架构,OpenSessionInView,以confluence这么广的使用,好像也没有听过这方面的问题投诉。


robbin:我写了一个简单的webapp在Tomcat5.5.12上面做了一个小测试。在JSP页面里面循环1万次输出字符串,程序在远程服务器上面运行,网络是ADSL宽带,filter确实被阻塞了20秒左右。然后我另外开了一个flashget去下载服务器上的大文件,模拟网络速度比较慢的环境,filter被阻塞了50秒左右。分别做了三次测试。另外当页面下载过程中直接点击浏览器stop按钮,则JSP执行被打断,filter立刻解除阻塞,被执行完毕。 


结论证明,使用OpenSessionInView的时候,如果render的页面数据量非常大,并且客户端网络速度很慢的情况下,由于页面的输出时间过程很长,确实会造成filter被长时间阻塞。对于OpenSessionInViewFilter来说,就会造成数据库连接被保持很长的时间,才能被关闭。 


不过,对于Spring的OpenSessionInViewFilter来说,虽然数据库连接被保持了过长的时间,但是并没有锁定数据库资源,特别是事务资源。因为Spring的事务是通过TransactionInterceptor来实现的,在MVC结构中,当最后一个业务bean被调用结束以后,Transaction就已经被提交了。此后,虽然数据库连接还保持中,但是数据库资源没有锁定问题。 


完整的调用示意图: 


request -> (OpenSessionInViewFilter打开Session) ->  ServletDispatcher -> Action -> (打开Connection,启动事务) -> spring bean -> another spring bean -> (提交事务) -> bean执行完毕,返回Action -> render view(JSP/Template) -> (OpenSessionInViewFilter关闭Session和Connection)

三、注意 
尽 管Open Session In View看起来还不错,其实副作用不少。看回上面OpenSessionInViewFilter的doFilterInternal方法代码,这个方法实际上是被父类的doFilter调用的,因此,我们可以大约了解的OpenSessionInViewFilter调用流程: 


request(请求)->open session并开始transaction->controller->View(Jsp)->结束transaction并 close session. 


一切看起来很正确,尤其是在本地开发测试的时候没出现问题,但试想下如果流程中的某一步被阻塞的话,那在这期间connection就一直被占用而不释放。最有可能被阻塞的就是在写Jsp这步,一方面可能是页面内容大,response.write的时间长,另一方面可能是网速慢,服务器与用户间传输时间久。当大量这样的情况出现时,就有连接池连接不足,造成页面假死现象。 


Open Session In View是个双刃剑,放在公网上内容多流量大的网站请慎用 


Hibernate 允许对关联对象、属性进行延迟加载,但是必须保证延迟加载的操作限于同一个 Hibernate Session 范围之内进行。如果 Service 层返回一个启用了延迟加载功能的领域对象给 Web 层,当 Web 层访问到那些需要延迟加载的数据时,由于加载领域对象的 Hibernate Session 已经关闭,这些导致延迟加载数据的访问异常
 (eg: org.hibernate.LazyInitializationException:(LazyInitializationException.java:42)
 - failed to lazily initialize a collection of role: cn.easyjava.bean.product.ProductType.childtypes, no session or session was closed
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: cn.easyjava.bean.product.ProductType.childtypes, no session or session was closed)。


猜你喜欢

转载自blog.csdn.net/sdaq23/article/details/78743162