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

为了避免 LazyLoadException,您使用了哪些替代策略?

我的确理解公开会议存在以下问题:

  • 运行在不同 jvm 中的分层应用程序
  • 事务只在结束时提交,而且您很可能希望在此之前得到结果。

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

70486 次浏览

因为在视图层中发送可能未初始化的代理,特别是集合,并从那里触发休眠加载,从性能和理解的角度来看都是麻烦的。

理解 :

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

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

表现 :

OSIV 倾向于将正确的实体加载隐藏起来——你倾向于不会注意到你的集合或者实体是延迟初始化的(也许是 N + 1)。更方便,更少控制。


Update: see OpenSessionInView 反模式 for a larger discussion regarding this subject. The author lists three important points:

  1. 每个惰性初始模式都会给你一个查询,这意味着每个实体都需要 N + 1个查询,其中 N 是惰性关联的数量。如果您的屏幕显示表格数据,那么读取 Hibernate 的日志就是一个很大的提示,表明您没有按照应该的方式进行操作
  2. 这完全打败了分层架构,因为你在表示层中用 DB 玷污了你的指甲。这是一个概念性的骗局,所以我可以接受它,但有一个必然结果
  3. 最后但并非最不重要的一点是,如果在获取会话时发生异常,那么异常将发生在页面的写入过程中: 您不能向用户显示一个干净的错误页面,您唯一能做的就是在主体中写入一个错误消息

如果你使用的是控制反转(IoC)容器,比如 Spring,你可能需要阅读一下 豆子范围。本质上,我告诉 Spring 给我一个 Hibernate Session对象,它的生命周期跨越整个请求(即,它在 HTTP 请求的开始和结束时被创建和销毁)。我不必担心 LazyLoadException或关闭会话,因为 IoC 容器为我管理这些。

如前所述,您必须考虑 N + 1 SELECT 性能问题。之后,您总是可以将 Hibernate 实体配置为在性能有问题的地方进行即时加载。

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

我不会说“公开会议在视野中”被认为是一种不好的做法; 是什么给了你这样的印象?

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

正如其他人指出的那样,与 OSIV 相比,有一些取舍——您更容易遇到 N + 1问题,因为您不太可能意识到自己正在启动什么事务。同时,这意味着您不需要更改服务层以适应视图中的细微变化。

我在冬眠方面很生疏。.但是我认为在一个 Hibernate 会话中有多个事务是可能的。因此,事务边界不必与会话启动/停止事件相同。

主要是因为我们可以避免在每次请求需要进行数据库访问时编写用于启动“持久上下文”(也称为会话)的代码。

在你的服务层,你可能需要调用那些有不同事务需求的方法,比如‘必需的,新的必需的,等等。’这些方法唯一需要的就是有人(比如说 OSIV 过滤器)已经启动了持久化上下文,所以他们唯一需要担心的就是——“嘿,给我这个线程的休眠会话。.我需要做一些数据库的工作”。

我只是在我的博客中写了一篇关于何时使用开放式会话的指导方针的文章。如果你感兴趣,可以去看看。

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

  • 事务可以在服务层提交-事务与 OSIV 无关。保持打开状态的是 Session,而不是正在运行的事务。

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

  • OSIV 是利用延迟加载的性能优势的一种很好的透明方式(也就是说,没有一个代码意识到它的发生)

以我个人的经验来看,OSIV 并没有那么糟糕。 我做的唯一安排是用两笔不同的交易: 第一个,打开在“服务层”,其中我有“业务逻辑” 第二个在视图渲染之前打开

这不会有太大帮助,但你可以在这里查看我的主题: * < a href = “ https://stackoverflow. com/questions/4758574/Hibernate-cache-1-OutOfMemory-with-OpenSessionInView”> Hibernate Cache1 OutOfMemory with OpenSessionInView

我有一些 OutOfMemory 的问题,因为 OpenSessionInView 和很多实体加载,因为他们停留在 Hibernate 缓存级别1,并没有垃圾收集(我加载了很多实体,每页500项,但所有实体留在缓存)

Open Session In View takes a bad approach to fetching data. Instead of letting the business layer decide how it’s best to fetch all the associations that are needed by the View layer, it forces the Persistence Context to stay open so that the View layer can trigger the Proxy initialization.

enter image description here

  • OpenSessionInViewFilter调用底层 SessionFactoryopenSession方法并获得一个新的 Session
  • SessionTransactionSynchronizationManager绑定。
  • OpenSessionInViewFilter调用 javax.servlet.FilterChain对象引用的 doFilter,并进一步处理请求
  • 调用 DispatcherServlet,它将 HTTP 请求路由到底层的 PostController
  • PostController调用 PostService以获取 Post实体的列表。
  • PostService打开一个新的事务,而 HibernateTransactionManager重用由 OpenSessionInViewFilter打开的同一个 Session
  • PostDAO在不初始化任何延迟关联的情况下获取 Post实体的列表。
  • PostService提交基础事务,但是 Session没有关闭,因为它是在外部打开的。
  • DispatcherServlet开始呈现 UI,而 UI 又导航延迟关联并触发它们的初始化。
  • OpenSessionInViewFilter可以关闭 Session,并释放底层数据库连接。

At a first glance, this might not look like a terrible thing to do, but, once you view it from a database perspective, a series of flaws start to become more obvious.

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

现在已经没有关注点分离了,因为语句是由服务层和 UI 呈现过程生成的。编写 断言生成的语句数需要遍历所有层(web、 service、 DAO)的集成测试,同时将应用程序部署在 Web 容器上。即使使用内存数据库(例如 HSQLDB)和轻量级 web 服务器(例如 Jetty) ,这些集成测试的执行速度也会慢于分层和使用数据库的后端集成测试,而前端集成测试完全是在嘲笑服务层。

UI 层仅限于导航关联,这反过来又会触发 N + 1查询问题。尽管 Hibernate 提供了 @BatchSize用于批量获取关联,而 FetchMode.SUBSELECT用于处理这种情况,但是注释会影响默认的获取计划,因此它们被应用到每个业务用例中。出于这个原因,数据访问层查询更为合适,因为它可以针对当前用例数据获取需求进行定制。

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

因此,要么连接保持的时间太长,要么为一个 HTTP 请求获取/释放多个连接,从而给底层连接池带来压力并限制可伸缩性。

弹簧靴

Unfortunately, 在 SpringBoot 中默认启用 Open Session in View.

So, make sure that in the application.properties configuration file, you have the following entry:

spring.jpa.open-in-view=false

这将禁用 OSIV,因此您可以通过在 EntityManager打开时获取所有需要的关联来正确处理 LazyInitializationException