【问题标题】:Why is Hibernate Open Session in View considered a bad practice?为什么 Hibernate Open Session in View 被认为是一种不好的做法?
【发布时间】:2010-11-09 08:32:39
【问题描述】:

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

我确实了解开放会话存在以下问题:

  • 在不同 jvm 中运行的分层应用程序
  • 事务仅在最后提交,而且您很可能想要之前的结果。

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

【问题讨论】:

  • OSIV 被认为是一种不好的做法吗?由谁?
  • 还有 - 什么是好的选择?
  • 这个来自 seam 开发者的和平文本:这个实现有几个问题,最严重的是我们永远无法确定事务是否成功,直到我们提交它,但是在“open session in view”事务被提交,视图被完全呈现,并且呈现的响应可能已经被刷新到客户端。我们如何通知用户他们的交易不成功?
  • 查看这篇博文了解利弊以及我自己的经验 - blog.jhades.org/open-session-in-view-pattern-pros-and-cons

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


【解决方案1】:

Open Session In View 获取数据的方法很糟糕。它不是让业务层决定如何最好地获取 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
  • PostDAO 获取 Post 实体列表,而不初始化任何惰性关联。
  • PostService 提交底层事务,但 Session 未关闭,因为它是在外部打开的。
  • DispatcherServlet 开始渲染 UI,然后导航惰性关联并触发它们的初始化。
  • OpenSessionInViewFilter可以关闭Session,同时释放底层数据库连接。

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

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

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

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

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

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

春季启动

很遗憾,Open Session in View is enabled by default in Spring Boot

所以,请确保在 application.properties 配置文件中,您有以下条目:

spring.jpa.open-in-view=false

这将禁用 OSIV,以便您可以正确处理 LazyInitializationException,方法是在 EntityManager 打开时获取所有需要的关联。

【讨论】:

  • 在视图中使用 Open Session 和自动提交是可能的,但不是 Hibernate 开发人员想要的方式。因此,尽管 Open Session in View 确实有其缺点,但自动提交并不是一个缺点,因为您可以简单地将其关闭并仍然使用它。
  • 会话保持打开状态。但交易没有。在整个过程中跨越事务也不是最优的,因为它增加了它的长度并且锁的持有时间超过了必要的时间。想象一下如果视图抛出 RuntimeException 会发生什么。事务是否会因为 UI 渲染失败而回滚?
  • 虽然我同意 OSIV 不是最理想的解决方案,但您提出的解决方法否定了像休眠这样的 ORM 的好处。 ORM 的目的是加速开发人员的体验,并在获取链接属性时要求开发人员返回编写 JPA 查询,而这恰恰相反。 Spring 通过默认启用 OSIV 并包括日志记录以通知开发人员已配置此功能,从而做到了这一点。
  • 好吧,你搞错了。仅仅因为 Hibernate 可以生成 CRUD 语句,并不意味着应用程序开发人员不应该使用查询。事实上,JPA 和 SQL 查询并不是例外,而是规则。 Spring 是一个很棒的框架,但默认启用 OSIV 是有害的。
  • @VladMihalcea 这是来自 Hibernate 官方文档的引述:“Hibernate 的设计目标是通过消除对手动、手工制作数据的需求,让开发人员从 95% 的常见数据持久性相关编程任务中解脱出来使用 SQL 和 JDBC 进行处理”。现在,您说 JPA 和 SQL 查询不是例外,而是规则。我觉得这两种说法是矛盾的。顺便说一句,我不反对你的回答,你已经很好地列出了来龙去脉。不过,我相信他们应该将文档中的 95% 改正为 70% 之类的东西:)
【解决方案2】:

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

了解

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

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

性能

OSIV 倾向于将适当的实体加载拖到地毯下——您往往不会注意到您的集合或实体是延迟初始化的(可能是 N+1 )。更方便,更少控制。


更新:请参阅The OpenSessionInView antipattern,了解有关此主题的更大讨论。作者列举了三个重点:

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

【讨论】:

  • 好的,它“污染”了具有休眠异常的视图层。但是,关于性能,我认为问题与访问将返回您的 dto 的服务层非常相似。如果您遇到性能问题,那么您应该使用更智能的查询或更轻量级的 dto 来优化该特定问题。如果您必须开发太多服务方法来处理视图中可能需要的可能性,那么您也在“污染”服务层。没有?
  • 一个区别是它延迟了休眠会话的关闭。您将等待 JSP 被渲染/写入/等,这会使对象在内存中的保留时间更长。这可能是个问题,尤其是当您需要在会话提交时写入数据时。
  • 说 OSIV 会损害性能是没有意义的。除了使用 DTO 之外,还有哪些选择?在这种情况下,您将总是具有较低的性能,因为任何视图使用的数据都必须加载,即使对于不需要它的视图也是如此。
  • 我认为污染是反过来的。如果我需要预先加载数据,逻辑层(或更糟糕的是数据访问层)需要知道对象将以哪种方式显示。改变视图,你最终会加载你不需要的东西或丢失你需要的对象。 Hibernate Exception 是一个错误,与任何其他意外异常一样具有毒害作用。但是性能是个问题。性能和可扩展性问题将迫使您在数据访问层投入更多的精力和工作,并可能迫使会话提前关闭
  • @JensSchauder “改变视图,你最终会加载你不需要的东西或丢失你需要的对象”。这正是它。如果您更改视图,最好加载您不需要的东西(因为您更有可能急于获取它们)或找出丢失的对象,因为您会得到延迟加载异常,而不是让视图加载它很懒惰,因为这会导致 N+1 问题,你甚至不会知道它正在发生。因此,IMO 最好让服务层(和您)知道它发送的内容,而不是延迟加载视图而您对此一无所知。
【解决方案3】:
  • 事务可以在服务层提交 - 事务与 OSIV 无关。保持打开状态的是Session,而不是正在运行的事务。

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

  • OSIV 是一种很好且透明(即 - 您的代码都没有意识到它发生)的方式,可以利用延迟加载的性能优势

【讨论】:

  • 关于第一个要点,对于来自 JBoss wiki 的原始 OSIV 至少不是这样,它还处理围绕请求的事务划分。
  • @PascalThivent 哪一部分让你这么想?
【解决方案4】:

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

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

正如其他人所指出的那样,OSIV 存在一些取舍 - 您更容易出现 N+1 问题,因为您不太可能意识到您正在启动哪些交易。同时,这意味着您无需更改服务层以适应视图中的微小变化。

【讨论】:

    【解决方案5】:

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

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

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

    【讨论】:

    • 您是否有一个指向通过请求范围的 bean 在视图中提供的 Hibernate 会话的实际实现的指针?
    【解决方案6】:

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

    【讨论】:

      【解决方案7】:

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

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

      【讨论】:

      • 作为一般的 SO 经验法则,如果您要提供答案,最好做的不仅仅是链接其他地方。也许提供一两个句子或列出的项目来给出要点。可以链接,但您想提供一点额外的价值。否则,您可能只想发表评论并将链接放在那里。
      • 这个答案中的链接值得一读,它为何时使用 OSIV 提供了很好的指导
      【解决方案8】:

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

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

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

      【讨论】:

        【解决方案9】:

        这不会有太大帮助,但您可以在此处查看我的主题: * Hibernate Cache1 OutOfMemory with OpenSessionInView

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

        【讨论】:

        • 如果你将这么多东西加载到 L1 缓存中,你的问题不是 OSIV,而是你设计了一些愚蠢的东西。
        猜你喜欢
        • 2013-09-13
        • 2010-10-22
        • 2011-11-20
        • 2010-10-15
        • 2011-05-14
        • 2010-09-26
        • 2010-11-04
        相关资源
        最近更新 更多