为什么在视图中的Hibernate开放会议被认为是一个不好的做法?

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

我明白,公开会议有问题:

  • 分层的应用程序运行在不同的jvm中
  • 事务只在最后被提交,而且很可能你会喜欢之前的结果。

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

因为在视图层发送可能未初始化的代理,特别是集合,并从性能和理解的angular度触发从那里加载hibernate。

理解

使用OSIV“污染”与数据访问层有关的视图层。

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

性能

OSIV倾向于在地毯下拖曳适当的实体 – 你往往不会注意到你的collections或实体是懒惰地初始化的(可能是N + 1)。 更方便,更less的控制。


更新:有关此主题的更多讨论,请参阅OpenSessionInView反模式 。 作者列出了三个重要的观点:

  1. 每个延迟初始化都会得到一个查询,这意味着每个实体都需要N + 1个查询,其中N是延迟关联的数目。 如果你的屏幕呈现表格数据,阅读Hibernate的日志是一个很大的暗示,你不这样做,你应该
  2. 这完全击败了分层架构,因为你在performance层中用数据库玷污了你的指甲。 这是一个概念,所以我可以忍受它,但有一个必然结果
  3. 最后但并非最不重要的是,如果在获取会话时发生exception,将在写入页面的过程中发生:您无法向用户显示干净的错误页面,您唯一能做的就是在主体中写入错误消息
  • 事务可以在服务层提交 – 事务与OSIV没有关系。 这是Session保持开放,而不是交易。

  • 如果您的应用程序层分布在多台机器上,那么您几乎不能使用OSIV–必须在通过线路发送对象之前初始化所需的所有内容。

  • OSIV是一个很好的和透明的(即你的代码没有意识到它发生)的方式来利用延迟加载的性能好处

对于更长的描述,您可以阅读我的打开会话在查看反模式文章。 否则,下面是为什么不应该使用“在视图中打开会话”的摘要。

在视图中打开会话不利于获取数据。 不要让业务层决定如何最好地获取View层所需的所有关联,而是强制Persistence Context保持打开状态,以便View层可以触发Proxy初始化。

在这里输入图像描述

  • OpenSessionInViewFilter调用底层SessionFactoryopenSession方法,并获得一个新的Session
  • Session被绑定到TransactionSynchronizationManager
  • OpenSessionInViewFilter调用javax.servlet.FilterChain对象引用的doFilter ,并进一步处理请求
  • 调用DispatcherServlet ,并将HTTP请求路由到基础PostController
  • PostController调用PostService来获取Post实体的列表。
  • PostService打开一个新的事务, HibernateTransactionManager重用与OpenSessionInViewFilter打开的Session相同的Session
  • PostDAO获取Post实体的列表,而不用初始化任何惰性关联。
  • PostService提交基础事务,但是Session没有closures,因为它是在外部打开的。
  • DispatcherServlet开始渲染用户界面,而用户界面又导航惰性关联并触发其初始化。
  • OpenSessionInViewFilter可以closuresSession ,并且底层的数据库连接也被释放。

乍看之下,这可能不是一件可怕的事情,但是一旦从数据库的angular度来看,一系列的缺陷开始变得更加明显。

服务层打开和closures一个数据库事务,但之后,没有明确的事务正在进行。 出于这个原因,从UI渲染阶段发出的每个附加语句都在自动提交模式下执行。 自动提交会对数据库服务器施加压力,因为每个语句都必须将事务日志刷新到磁盘,因此会在数据库端造成大量I / O通信。 一种优化是将Connection标记为只读,这将允许数据库服务器避免写入事务日志。

由于语句是由服务层和UI渲染过程生成的,因此不再有问题分离。 编写集成testing, 声明生成的语句数量需要通过所有图层(web,service,DAO),同时将应用程序部署在web容器上。 即使在使用内存数据库(例如HSQLDB)和轻量级Web服务器(例如Jetty)时,这些集成testing的执行速度也比分层分离和后端集成testing使用数据库要慢前端集成testing完全嘲弄服务层。

UI层仅限于导航关联,而关联又可以触发N + 1个查询问题。 尽pipeHibernate提供了批量获取关联的FetchMode.SUBSELECT ,而FetchMode.SUBSELECT可以处理这个场景,但是注释正在影响默认的获取计划,所以它们被应用于每个业务用例。 出于这个原因,数据访问层查询更合适,因为它可以针对当前的用例数据获取需求进行定制。

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

因此,无论是连接持续时间过长,要么是针对单个HTTP请求获取/释放多个连接,都会对底层连接池施加压力并限制可伸缩性。

春季启动

不幸的是, 在Spring Boot中默认启用View中的Open Session 。

因此,请确保在application.propertiesconfiguration文件中有以下条目:

 spring.jpa.open-in-view=false 

这将禁用OSIV,以便您可以正确处理LazyInitializationException

我不会说Open View In View被认为是不好的做法, 什么给你的印象?

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

正如其他人指出的,OSIV有一些权衡 – 你更容易出现N + 1问题,因为你不太可能意识到你要开始什么交易。 同时,这意味着你不需要改变你的服务层来适应你视图中的微小变化。

如果您使用的是像Spring这样的控制反转(IoC)容器,那么您可能需要阅读bean范围 。 从本质上讲,我告诉Spring给我一个Hibernate Session对象,它的生命周期跨越整个请求(即在HTTP请求的开始和结束时被创build和销毁)。 我不必担心LazyLoadException也不必closures会话,因为IoC容器为我pipe理。

如前所述,您将不得不考虑N + 1个SELECT性能问题。 你可以随时configuration你的Hibernate实体在性能问题的地方进行急切的连接加载。

这个bean的范围解决scheme并不是Spring特有的。 我知道PicoContainer提供相同的function,我相信其他成熟的IoC容器提供了类似的东西。

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

我只是在一些指导方针上写了一篇文章,指出在博客中何时使用open session。 检查一下,如果你感兴趣。

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

我对Hibernate生锈..但我认为它可能在一个Hibernate会话中有多个事务。 所以您的事务边界不必与会话开始/停止事件相同。

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

在您的服务层,您可能需要调用具有不同事务需求的方法,例如“Required,New Required”等。 这些方法唯一需要的是有人(即OSIVfilter)启动了持久化上下文,所以他们唯一担心的就是 – “嘿,给我这个线程的hibernate会话..我需要做一些数据库的东西“。

这不会有太大的帮助,但你可以在这里检查我的主题:* Hibernate Cache1 OutOfMemory与OpenSessionInView

我有一些OutOfMemory的问题,因为OpenSessionInView和大量的实体加载,因为他们留在Hibernatecaching级别1,并没有垃圾收集(我加载了大量的实体每页500个项目,但所有实体留在caching)