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

HeD*_*ges 104 java hibernate jpa lazy-loading open-session-in-view

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

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

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

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

Rob*_*anu 45

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

理解:

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

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

表现:

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


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

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

  • 好吧,它使用hibernate异常"污染"视图层.但是,关于性能,我认为问题非常类似于访问将返回您的dto的服务层.如果您遇到性能问题,那么您应该使用更智能的查询或更轻量级的dto来优化该特定问题.如果您必须开发太多服务方法来处理视图中可能需要的可能性,那么您也在"污染"服务层.没有? (13认同)
  • 我认为污染反过来了.如果我需要急切加载数据,逻辑层(或更糟糕的数据访问层)需要知道对象将以何种方式显示.更改视图,最终加载您不需要的东西或缺少您需要的对象.Hibernate异常是一个错误,就像任何其他意外异常一样中毒.但性能是一个问题.性能和可伸缩性问题将迫使您在数据访问层中加入更多思考和工作,并可能迫使会话提前关闭 (11认同)
  • 说OSIV伤害性能是没有意义的.除了使用DTO之外还有哪些替代方案?在这种情况下,您将*总是*具有较低的性能,因为即使对于不需要它的视图,也必须加载任何视图使用的数据. (8认同)
  • @JensSchauder “改变视图,你最终会加载你不需要的东西或丢失你需要的对象”。这正是它。如果您更改视图,最好加载不需要的内容(因为您更有可能急切地获取它们)或找出丢失的对象,因为您会遇到延迟加载异常,而不是让视图加载它懒惰,因为这将导致 N+1 问题,您甚至不会知道它正在发生。因此,IMO 更好的是服务层(和您)知道它发送的是什么,而不是懒惰地加载视图,而您对此一无所知。 (2认同)

Vla*_*cea 39

有关更长的说明,您可以阅读我在视图反模式中的开放会话文章.否则,这里是您不应该在视图中使用Open Session的原因摘要.

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

在此输入图像描述

  • OpenSessionInViewFilter呼叫的openSession底层的方法SessionFactory,并获得新的Session.
  • Session是必然的TransactionSynchronizationManager.
  • OpenSessionInViewFilter呼叫doFilter的的javax.servlet.FilterChain对象引用和所述请求被进一步处理
  • DispatcherServlet被调用,并且它的路由HTTP请求到下层PostController.
  • PostController呼叫PostService拿到名单Post的实体.
  • PostService打开一个新的事务,而HibernateTransactionManager重用相同Session,是由打开的OpenSessionInViewFilter.
  • PostDAO获取的名单Post没有任何初始化懒关联的实体.
  • PostService承诺基本交易,但Session不是封闭的,因为它是从外部打开.
  • DispatcherServlet开始渲染的UI,这反过来,导航懒惰协会,并触发其初始化.
  • OpenSessionInViewFilter可以关闭Session,和底层数据库连接被释放为好.

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

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

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

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

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

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

春季启动

遗憾的是,Spring Boot中默认启用Open Session in View.

因此,请确保在application.properties配置文件中包含以下条目:

spring.jpa.open-in-view=false
Run Code Online (Sandbox Code Playgroud)

这将禁用OSIV,以便您可以正确的方式处理LazyInitializationException.

  • 在View中使用自动提交的Open Session是可能的,但不是Hibernate开发人员想要的方式.因此,虽然Open Session in View确实有它的缺点,但自动提交不是一个因为你可以简单地关闭它并仍然使用它. (3认同)
  • 尽管我同意 OSIV 不是最理想的解决方案,但您提出的解决方案否定了像 hibernate 这样的 ORM 的好处。ORM 的目的是加速开发人员的体验,并要求开发人员在获取链接属性时重新编写 JPA 查询,效果恰恰相反。Spring 通过默认启用 OSIV 并包括日志记录来通知开发人员已配置,从而实现了这一点。 (3认同)
  • 好吧,你全错了。仅仅因为 Hibernate 可以生成 CRUD 语句,并不意味着应用程序开发人员不应该使用查询。事实上,JPA 和 SQL 查询并不是例外,而是规则。Spring 是一个很棒的框架,但是默认启用 OSIV 是有害的。 (3认同)
  • 会议仍然开放.但交易没有.在整个过程中跨越事务并不是最佳的,因为它增加了它的长度并且锁被保持的时间超过了必要的时间.想象一下,如果视图抛出RuntimeException会发生什么.由于UI呈现失败,事务是否会回滚? (2认同)

Boz*_*zho 24

  • 事务可以在服务层中提交 - 事务与OSIV无关.这Session是保持开放,而不是交易 - 运行.

  • 如果您的应用程序层分布在多台机器上,那么您几乎无法使用OSIV - 您必须在通过网络发送对象之前初始化您需要的所有内容.

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

  • 关于第一个要点,对于来自JBoss wiki的原始[OSIV](http://community.jboss.org/wiki/OpenSessioninView)至少不是这样,它还处理请求周围的事务划分. (2认同)

Geo*_*man 13

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

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

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


0su*_*ain 5

如果您使用的是Inversion of Control(IoC)容器(如Spring),您可能需要阅读bean范围.本质上,我告诉Spring给我一个Hibernate Session对象,其生命周期跨越整个请求(即,它在HTTP请求的开始和结束时被创建和销毁).LazyLoadException因为IoC容器为我管理,所以我不必担心s或关闭会话.

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

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