为什么Hibernate Open Session in View被认为是一种不好的做法?

时间:2009-07-09 11:49:45

标签: java hibernate jpa lazy-loading open-session-in-view

您使用什么样的替代策略来避免LazyLoadExceptions?

我确实理解视图中的开放会话存在以下问题:

  • 分层应用程序在不同的jvm中运行
  • 交易只在最后提交,很可能你以前想要的结果。

但是,如果您知道您的应用程序在单个虚拟机上运行,​​为什么不通过在视图策略中使用开放会话来减轻您的痛苦?

9 个答案:

答案 0 :(得分:42)

因为在视图层中发送可能未初始化的代理,尤其是集合,并从性能和理解的角度来看,触发休眠加载可能会产生麻烦。

了解

使用OSIV'污染'视图层,其中涉及与数据访问层相关的问题。

视图层不准备处理延迟加载时可能发生的HibernateException,但可能是数据访问层。

性能

OSIV倾向于在地毯下拖拽正确的实体加载 - 您往往不会注意到您的集合或实体被懒惰地初始化(可能是N + 1)。更方便,更少控制。


更新:有关此主题的更多讨论,请参阅The OpenSessionInView antipattern。作者列出了三个要点:

  
      
  1. 每个延迟初始化都会得到一个查询,这意味着每个实体都需要N + 1个查询,其中N是延迟关联的数量。如果您的屏幕显示表格数据,那么阅读Hibernate的日志是一个很大的暗示,你不应该这样做
  2.   
  3. 这完全打败了分层架构,因为你在表达层中用DB玷污你的指甲。这是一个概念性的骗局,所以我可以忍受它,但有一个推论
  4.   
  5. 最后但并非最不重要的是,如果在获取会话时发生异常,则会在页面写入期间发生:您无法向用户显示干净的错误页面,您唯一能做的就是写入错误消息身体
  6.   

答案 1 :(得分:34)

要获得更长的说明,您可以阅读我的Open Session In View Anti-Pattern文章。否则,这里是您不应该在视图中使用Open Session的原因摘要。

Open View In View采用不好的方法来获取数据。它不是让业务层决定如何最好地获取View层所需的所有关联,而是强制Persistence Context保持打开状态,以便View层可以触发Proxy初始化。

enter image description here

  • OpenSessionInViewFilter调用基础openSession的{​​{1}}方法,并获得新的SessionFactory
  • Session绑定到TransactionSynchronizationManager
  • Session调用OpenSessionInViewFilter对象引用的doFilter并进一步处理请求
  • 调用DispatcherServlet,并将HTTP请求路由到基础javax.servlet.FilterChain
  • PostController调用PostController获取PostService个实体的列表。
  • Post会打开一个新的交易,而PostService会重复使用HibernateTransactionManager打开的Session
  • OpenSessionInViewFilter在不初始化任何延迟关联的情况下提取PostDAO个实体列表。
  • Post提交基础交易,但PostService未关闭,因为它是在外部打开的。
  • Session开始呈现UI,然后导航延迟关联并触发初始化。
  • DispatcherServlet可以关闭OpenSessionInViewFilter,也会释放基础数据库连接。

乍一看,这可能看起来不太可怕,但是,一旦从数据库的角度来看,一系列缺陷就会变得更加明显。

服务层打开和关闭数据库事务,但之后,没有显式事务继续进行。因此,从UI呈现阶段发出的每个附加语句都以自动提交模式执行。自动提交会对数据库服务器施加压力,因为每个语句都必须将事务日志刷新到磁盘,因此会在数据库端产生大量I / O流量。一个优化是将Session标记为只读,这将允许数据库服务器避免写入事务日志。

不再存在关注点分离,因为语句由服务层和UI呈现过程生成。编写assert the number of statements being generated需要遍历所有层(Web,服务,DAO)的集成测试,同时将应用程序部署在Web容器上。即使使用内存数据库(例如HSQLDB)和轻量级Web服务器(例如Jetty),这些集成测试的执行速度也会比分层和后端集成测试使用数据库的速度慢,而前端集成测试完全嘲笑服务层。

UI层仅限于导航关联,这可能反过来触发N + 1个查询问题。尽管Hibernate提供@BatchSize用于批量获取关联,而FetchMode.SUBSELECT用于处理此方案,但注释正在影响默认提取计划,因此它们将应用于每个业务用例。因此,数据访问层查询更加合适,因为它可以根据当前用例数据获取要求进行定制。

最后但并非最不重要的是,数据库连接可以在整个UI呈现阶段(取决于您的连接释放模式)保持,这会增加连接租用时间并限制由于数据库连接池拥塞而导致的整体事务吞吐量。连接越多,其他并发请求等待从池中获取连接的次数就越多。

因此,要么连接保持时间过长,要么为单个HTTP请求获取/释放多个连接,从而对底层连接池施加压力并限制可伸缩性。

Spring Boot

不幸的是,Open Session in View is enabled by default in Spring Boot

因此,请确保在Connection配置文件中,您有以下条目:

application.properties

这将禁用OSIV,以便您handle the LazyInitializationException the right way

答案 2 :(得分:24)

  • 事务可以在服务层中提交 - 事务与OSIV无关。这是Session保持开放,而不是交易 - 正在运行。

  • 如果您的应用程序层分布在多台计算机上,那么您几乎无法使用OSIV - 您必须在通过网络发送对象之前初始化所需的一切。

  • OSIV是一个很好的透明(即 - 你的代码都没有意识到它发生了)利用延迟加载的性能优势的方法

答案 3 :(得分:13)

我不会说Open Session In View被认为是一种不好的做法;什么给你这种印象?

Open-Session-In-View是一种处理Hibernate会话的简单方法。因为它很简单,有时简单化。如果您需要对事务进行细粒度控制,例如在请求中有多个事务,Open-Session-In-View并不总是一个好方法。

正如其他人所指出的那样,对OSIV进行一些权衡 - 你更容易出现N + 1问题,因为你不太可能意识到你正在开展什么交易。同时,这意味着您无需更改服务图层即可适应视图中的微小更改。

答案 4 :(得分:5)

如果您使用的是Inversion of Control(IoC)容器(如Spring),则可能需要阅读bean scoping。本质上,我告诉Spring给我一个Hibernate Session对象,其生命周期跨越整个请求(即,它在HTTP请求的开始和结束时被创建和销毁)。我不必担心LazyLoadException也不会关闭会话,因为IoC容器会为我管理。

如前所述,您将不得不考虑N + 1 SELECT性能问题。您可以随后配置您的Hibernate实体,以便在性能有问题的地方进行热切加入。

bean范围界定解决方案不是特定于Spring的。我知道PicoContainer提供相同的功能,我相信其他成熟的IoC容器也能提供相似的功能。

答案 5 :(得分:4)

根据我自己的经验,OSIV并不是那么糟糕。 我做的唯一安排是使用两个不同的交易: - 第一个,在“服务层”中打开,我有“业务逻辑” - 第二个在视图渲染之前打开

答案 6 :(得分:3)

我刚刚在我的博客中发布了一些关于何时在视图中使用开放会话的指南。如果您有兴趣,请查看。

http://heapdump.wordpress.com/2010/04/04/should-i-use-open-session-in-view/

答案 7 :(得分:1)

我在Hibernate上生锈了...但我认为可能在一个Hibernate会话中有多个事务。因此,您的交易边界不必与会话开始/停止事件相同。

OSIV,imo,主要是有用的,因为每次请求需要进行数据库访问时,我们都可以避免编写用于启动“持久性上下文”(a.k.a.会话)的代码。

在您的服务层中,您可能需要调用具有不同交易需求的方法,例如“必需,新要求等”。这些方法唯一需要的是某人(即OSIV过滤器)启动了持久化上下文,所以他们唯一需要担心的是 - “嘿,给我这个线程的hibernate会话......我需要做一些DB的东西“。

答案 8 :(得分:1)

这不会太多,但你可以在这里查看我的主题: * Hibernate Cache1 OutOfMemory with OpenSessionInView

我有一些OutOfMemory问题,因为OpenSessionInView和很多实体加载,因为它们保留在Hibernate缓存level1并且不是垃圾收集(我加载了很多实体,每页有500个项目,但所有实体都保留在缓存中)