ChatGPT解决这个技术问题 Extra ChatGPT

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

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

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

在不同 jvm 中运行的分层应用程序

事务仅在最后提交,并且很可能您想要之前的结果。

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

OSIV 被认为是一种不好的做法吗?通过谁?
而且 - 什么是好的选择?
如果来自 seam 开发人员的文本和平:这个实现有几个问题,最严重的是,在我们提交之前,我们永远无法确定事务是否成功,但是当“视图中的打开会话”事务被提交时,视图已完全呈现,并且呈现的响应可能已经刷新到客户端。我们如何通知用户他们的交易不成功?
有关利弊以及我自己的经验,请参阅此博客文章 - blog.jhades.org/open-session-in-view-pattern-pros-and-cons

V
Vlad Mihalcea

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

https://i.stack.imgur.com/6jtkC.png

OpenSessionInViewFilter 调用底层 SessionFactory 的 openSession 方法,获取一个新的 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,以便您可以通过在 EntityManager 打开时获取所有需要的关联来正确处理 LazyInitializationException


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

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

理解:

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

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

表现:

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

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

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


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

事务可以在服务层提交——事务与 OSIV 无关。这是保持打开状态的会话,而不是正在运行的事务。

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

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


关于第一个要点,对于来自 JBoss wiki 的原始 OSIV 至少不是这样,它还处理围绕请求的事务划分。
@PascalThivent 哪一部分让你这么认为?
G
Geoffrey Wiseman

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

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

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


0
0sumgain

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

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

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


您是否有指向通过请求范围 bean 在视图中提供的 Hibernate 会话的实际实现的指针?
D
Davide

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


C
Chris Upton

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

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


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

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

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

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


C
Community

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

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


如果您将这么多东西加载到 L1 缓存中,那么您的问题不是 OSIV,而是您设计了一些愚蠢的东西。