为什么将View中的Hibernate Open Session视为不良做法?


108

以及您使用哪种替代策略来避免LazyLoadExceptions?

我确实了解到公开会议存在以下问题:

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

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


12
OSIV是否被视为不良做法?通过谁?
约翰内斯·布罗德沃尔

4
而且-有什么好的选择?
David Rabinowitz,2009年

7
如果是接缝开发人员,那么这种文本的和平:这种实现存在几个问题,最严重的是,在提交该事务之前,我们永远无法确定某个事务是否成功,但是在提交“公开会话中的事务”时,该视图已完全呈现,并且呈现的响应可能已经被刷新到客户端。我们如何通知用户他们的交易不成功?
darpet


2
请参阅此博客文章的优缺点以及我对此的经验-blog.jhades.org/open-session-in-view-pattern-pros-and-cons
Angular University

Answers:


46

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

了解

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

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

性能

OSIV倾向于在地毯下拖拉适当的实体负载-您往往不会注意到自己的集合或实体是延迟初始化的(也许是N + 1)。更多便利,更少控制。


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

  1. 每个惰性初始化都会为您提供一个查询,这意味着每个实体将需要N + 1个查询,其中N是惰性关联的数量。如果屏幕上显示表格数据,则读取Hibernate日志是一个很大的提示,表明您不应该这样做
  2. 这完全破坏了分层体系结构,因为您在演示层中不满意DB。这是一个概念上的弊端,所以我可以接受,但是有一个必然结果
  3. 最后但并非最不重要的一点是,如果在获取会话时发生异常,则该异常将在页面的编写过程中发生:您无法向用户展示干净的错误页面,唯一可以做的就是在正文中写入错误消息

13
好的,它会休眠状态下“污染”视图层。但是,关于性能,我认为问题与访问将返回dto的服务层非常相似。如果遇到性能问题,则应使用更智能的查询或更轻量级的dto优化该特定问题。如果您必须开发太多的服务方法来处理视图中可能需要的可能性,那么您还将“污染”服务层。没有?
HeDinges

1
一个区别是它延迟了Hibernate会话的关闭。您将等待JSP的渲染/编写等,这会将对象保留在内存中的时间更长。这可能是个问题,特别是如果您需要在会话提交时写入数据时。
罗伯特·蒙提亚努

8
说OSIV会损害性能是没有道理的。除了使用DTO之外,还有哪些替代方案?在这种情况下,您将始终具有较低的性能,因为即使对于不需要的视图,任何视图使用的数据也必须加载。
约翰内斯·布罗德沃尔

11
我认为污染反过来起作用。如果我急于加载数据,逻辑层(或更糟糕的是数据访问层)需要知道将以哪种方式显示对象。更改视图,您最终会加载不需要的东西或缺少所需的对象。休眠异常是一个错误,并且与任何其他意外异常一样中毒。但是性能是一个问题。性能和可伸缩性问题将迫使您在数据访问层中投入更多的精力和精力,并可能迫使会话更早关闭
Jens Schauder 2009年

1
@JensSchauder“更改视图,最终将加载不需要的内容或缺少所需的对象”。就是这样 如果更改视图,则最好加载不需要的东西(因为您更有可能急于获取它们)或找出丢失的对象(因为您会获得Lazy加载例外),而不是让视图加载懒惰,因为这将导致N + 1问题,您甚至都不知道它正在发生。因此,与懒惰地加载视图相比,IMO更好地使服务层(和您)知道发送了什么,而您对此一无所知。
Jeshurun

40

有关详细说明,您可以阅读我的“在视图中使用反模式打开会话”一文。否则,这是为什么不应该在View中使用Open Session的摘要。

在视图中打开会话采用了一种错误的方法来获取数据。与其让业务层决定如何最好地获取View层所需的所有关联,不如让持久性上下文保持打开状态,以便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默认情况下启用了View中的Open Session

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

spring.jpa.open-in-view=false

这将禁用OSIV,这样就可以处理LazyInitializationException的正确方法


3
可以将View中的Open Session与自动提交一起使用,但是不能像Hibernate开发人员那样使用。因此,尽管View中的Open Session确实有其缺点,但是自动提交不是一个原因,因为您可以简单地将其关闭并仍然使用它。
stefan.m

您正在谈论事务内部发生的事情,这是事实。但是Web层渲染阶段发生在Hibernate之外,因此您将获得自动提交模式。说得通?
弗拉德·米哈尔切阿

我认为这不是View中Open Session的最佳选择。会话和事务应保持打开状态,直到呈现视图为止,然后无需自动提交模式。
stefan.m 2016年

2
会话保持打开状态。但是交易没有。在整个过程中跨越事务也不是最佳选择,因为这会增加其长度,并且锁的持有时间超过了必要。想象一下,如果视图抛出RuntimeException会发生什么。UI渲染失败会导致事务回滚吗?
弗拉德·米哈尔恰

非常感谢您提供非常详细的答案!最后,我只会更改指南,因为Spring Boot用户可能不会以这种方式使用jpa。
Skeeve

24
  • 事务可以在服务层中提交-事务与OSIV不相关。这是Session该保持打开状态,而不是一个交易-运行。

  • 如果您的应用程序层分布在多台计算机上,那么您几乎不能使用OSIV-您必须初始化所需的所有内容,然后才能通过网络发送对象。

  • OSIV是一种利用延迟加载的性能优势的不错且透明的方法(即,您的代码都不知道它发生了)


2
关于第一个要点,至少对于JBoss Wiki上的原始OSIV而言并非如此,它还处理围绕请求的事务划分。
Pascal Thivent 2010年

@PascalThivent哪一部分让您这么认为?
Sanghyun Lee

13

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

视图中打开会话是一种使用Hibernate处理会话的简单方法。因为它很简单,所以有时过于简单。如果您需要对事务进行细粒度的控制,例如在一个请求中有多个事务,则“打开会话中的视图”并不总是一种好方法。

正如其他人指出的那样,在OSIV上需要权衡取舍-您更容易遇到N + 1问题,因为您不太可能意识到自己正在进行的交易。同时,这意味着您无需更改服务层即可适应视图中的微小更改。


5

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

如前所述,您将不得不考虑N + 1 SELECT性能问题。之后,您始终可以将Hibernate实体配置为在性能存在问题的地方进行急切的联接加载。

Bean作用域解决方案不是特定于Spring的。我知道PicoContainer提供相同的功能,并且我确定其他成熟的IoC容器也可以提供类似的功能。


1
您是否有一个指向Hibernate会话的实际实现的指针,该会话通过请求作用域的bean在视图中可用?
Marvo

4

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


3

我刚刚发布了一些有关何时在博客中使用公开会话的指导原则的文章。如果您有兴趣,请查看。

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


1
作为一般的经验法则,如果要提供答案,则最好做的不只是链接其他地方。也许提供一两个句子或列出要点。可以链接,但是您想提供一些额外的价值。否则,您可能只想评论并将链接放在此处。
DWright

在这个答案的链接是值得一读,它提供了何时使用OSIV一个很好的指导,而不是
AMS

1

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

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

在服务层中,您可能需要调用具有不同事务需求的方法,例如“ Required”,“ New Required”等。这些方法唯一需要的是有人(例如OSIV筛选器)已经启动了持久性上下文,因此他们唯一需要担心的是-“嘿,给我这个线程的休眠会话。我需要做一些事情。 DB资料”。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.