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

问题描述 投票:99回答:9

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

我确实理解在视图中打开会话有以下问题:

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

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

java hibernate jpa lazy-loading open-session-in-view
9个回答
42
投票

因为在视图层中发送可能未初始化的代理(尤其是集合)并从中触发休眠加载可能会从性能和理解的角度来解决问题。

理解:

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

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

性能:

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


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

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

34
投票

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

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

enter image description here

  • OpenSessionInViewFilter称为基础openSessionSessionFactory方法并获得新的Session
  • SessionTransactionSynchronizationManager绑定。
  • qazxsw poi调用qazxsw poi对象引用的qazxsw poi并且请求被进一步处理
  • 调用OpenSessionInViewFilter,它将HTTP请求路由到底层的doFilter
  • javax.servlet.FilterChainDispatcherServlet获得PostController实体列表。
  • PostController开启了一项新的交易,PostService重新使用了由Post打开的PostService
  • HibernateTransactionManager获取Session实体列表而不初始化任何惰性关联。
  • OpenSessionInViewFilter提交基础交易,但PostDAO没有关闭,因为它是在外部开放的。
  • Post开始渲染UI,而UI又导航惰性关联并触发其初始化。
  • PostService可以关闭Session,也会释放底层数据库连接。

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

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

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

UI层仅限于导航关联,这可以反过来触发N + 1个查询问题。虽然Hibernate提供了Session来批量获取关联,而Connection可以应对这种情况,但是注释正在影响默认的获取计划,因此它们可以应用于每个业务用例。出于这个原因,数据访问层查询更加合适,因为它可以针对当前用例数据获取要求进行定制。

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

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

春季启动

不幸的是,assert the number of statements being generated

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

FetchMode.SUBSELECT

这将禁用OSIV,以便您可以Open Session in View is enabled by default in Spring Boot


24
投票
  • 事务可以在服务层中提交 - 事务与OSIV无关。这是application.properties保持开放,而不是交易 - 运行。
  • 如果您的应用程序层分布在多台机器上,那么您几乎无法使用OSIV - 您必须在通过网络发送对象之前初始化您需要的所有内容。
  • OSIV是一个很好的透明(即 - 你的代码都没有意识到它发生了)的方式来利用延迟加载的性能优势

13
投票

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

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

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


5
投票

如果您使用的是Inversion of Control(IoC)容器(如Spring),您可能需要阅读spring.jpa.open-in-view=false 。本质上,我告诉Spring给我一个Hibernate handle the LazyInitializationException the right way对象,其生命周期跨越整个请求(即,它在HTTP请求的开始和结束时被创建和销毁)。我不必担心Sessions也不必关闭会话,因为IoC容器为我管理。

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

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


4
投票

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


3
投票

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

bean scoping


1
投票

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

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

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


1
投票

这不会太多,但你可以在这里查看我的主题:* Session

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

© www.soinside.com 2019 - 2024. All rights reserved.