代码之家  ›  专栏  ›  技术社区  ›  HeDinges

为什么hibernate open session在视图中被认为是一种不好的做法?

  •  97
  • HeDinges  · 技术社区  · 17 年前

    你用什么样的策略来避免懒散的加载异常?

    我确实理解,鉴于公开会议存在以下问题:

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

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

    9 回复  |  直到 9 年前
        1
  •  45
  •   Robert Munteanu    15 年前

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

    理解 :

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

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

    性能 :

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


    更新: 看见 The OpenSessionInView antipattern 关于这个问题的更大的讨论。作者列举了三个要点:

    1. 每次延迟初始化都会得到一个查询,这意味着每个实体都需要n+1个查询,其中n是延迟关联的数量。如果屏幕显示的是表格数据,那么阅读hibernate的日志是一个很大的提示,说明您没有按照您应该做的那样做
    2. 这完全破坏了分层体系结构,因为您在表示层中用db玷污了指甲。这是一个概念上的骗局,所以我可以接受,但有一个推论
    3. 最后但并非最不重要的是,如果在获取会话时发生异常,则会在写入页时发生:您不能向用户呈现干净的错误页,唯一可以做的是在正文中写入错误消息
        2
  •  39
  •   Vlad Mihalcea    8 年前

    要了解更多的描述,您可以阅读我的 Open Session In View Anti-Pattern 文章。否则,这里将总结为什么不应该在视图中使用open session。

    视图中的打开会话获取数据的方法不正确。它不让业务层决定如何最好地获取视图层所需的所有关联,而是强制持久性上下文保持打开状态,以便视图层可以触发代理初始化。

    enter image description here

    • 这个 OpenSessionInViewFilter 调用 openSession 基础方法 SessionFactory 得到一个新的 Session .
    • 这个 会话 TransactionSynchronizationManager .
    • 这个 OpenSessionInviewFilter 调用 doFilter javax.servlet.FilterChain 对象引用并进一步处理请求
    • 这个 DispatcherServlet 调用,并将http请求路由到底层 PostController .
    • 这个 后控制器 调用 PostService 获取 Post 实体。
    • 这个 售后服务 打开一个新事务,然后 HibernateTransactionManager 重复使用相同的 会话 是由 OpenSessionInviewFilter .
    • 这个 PostDAO 获取的列表 不初始化任何惰性关联的实体。
    • 这个 售后服务 提交基础事务,但 会话 未关闭,因为它是在外部打开的。
    • 这个 分发器 开始呈现ui,ui依次导航惰性关联并触发它们的初始化。
    • 这个 OpenSessionInviewFilter 可以关闭 会话 ,并且底层数据库连接也将被释放。

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

    服务层打开和关闭数据库事务,但是之后,没有显式事务发生。因此,从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,以便您可以 handle the LazyInitializationException the right way .

        3
  •  24
  •   Bozho    15 年前
    • 事务可以在服务层提交-事务与osiv无关。这是 Session 它保持打开状态,而不是事务运行。

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

    • osiv是一种很好的、透明的(即,您的代码都不知道它会发生)方法,可以利用延迟加载的性能优势

        4
  •  13
  •   Geoffrey Wiseman    17 年前

    我不认为公开会议被认为是一种不好的做法;是什么给你这种印象?

    视图中的打开会话是使用hibernate处理会话的简单方法。因为它很简单,有时也很简单。如果您需要对事务进行细粒度的控制,例如在请求中有多个事务,那么视图中的打开会话并不总是一个好方法。

    正如其他人所指出的,osiv有一些折衷方案——你更容易遇到n+1问题,因为你不太可能意识到你正在启动什么样的事务。同时,这意味着您不需要更改服务层以适应视图中的细微更改。

        5
  •  5
  •   0sumgain    17 年前

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

    如前所述,您将不得不考虑n+1选择性能问题。之后,您始终可以配置hibernate实体,以便在性能有问题的地方执行紧急连接加载。

    bean作用域解决方案不是特定于spring的。我知道picocontainer提供了相同的功能,我相信其他成熟的ioc容器也提供了类似的功能。

        6
  •  4
  •   Davide    15 年前

    以我自己的经验来看,osiv还不错。 我唯一的安排是使用两种不同的交易: -第一个,在“服务层”中打开,在这里我有“业务逻辑” -第二个在视图呈现之前打开

        7
  •  3
  •   Chris Upton    16 年前

    我刚刚在博客上写了一篇关于何时在视图中使用开放会话的指导原则的文章。如果你感兴趣的话可以去看看。

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

        8
  •  1
  •   rjk2008    16 年前

    我正在冬眠中。但我认为在一个hibernate会话中有多个事务是可能的。因此,事务边界不必与会话开始/停止事件相同。

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

    在您的服务层中,您可能需要调用具有不同事务需求的方法,例如“Required、New Required等”。这些方法唯一需要的是有人(即OSIV过滤器)已经启动了持久性上下文,因此它们只需要处理关于是-“嘿给我这个线程的休眠会话..我需要做些数据库的工作”。

        9
  •  1
  •   Community Mohan Dere    9 年前

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

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

    推荐文章