您使用什么样的替代策略来避免 LazyLoadExceptions?
我确实了解开放式会话存在以下问题:
- 在不同 jvm 中运行的分层应用程序
- 事务仅在最后提交,并且很可能您想要之前的结果。
但是,如果您知道您的应用程序在单个虚拟机上运行,为什么不使用视图中的开放会话策略来减轻您的痛苦呢?
您使用什么样的替代策略来避免 LazyLoadExceptions?
我确实了解开放式会话存在以下问题:
但是,如果您知道您的应用程序在单个虚拟机上运行,为什么不使用视图中的开放会话策略来减轻您的痛苦呢?
Open Session In View 采用了一种糟糕的方法来获取数据。它不是让业务层决定如何最好地获取 View 层所需的所有关联,而是强制 Persistence Context 保持打开状态,以便 View 层可以触发代理初始化。
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
这将禁用 OSIV,以便您可以LazyInitializationException
通过在打开时获取所有需要的关联来正确处理EntityManager
。
因为在视图层中发送可能未初始化的代理,尤其是集合,并从那里触发休眠加载,从性能和理解的角度来看都可能会带来麻烦。
理解:
使用 OSIV 会“污染”与数据访问层相关的视图层。
视图层不准备处理HibernateException
延迟加载时可能发生的情况,但可能是数据访问层。
性能:
OSIV 倾向于将适当的实体加载拖到地毯下——您往往不会注意到您的集合或实体是延迟初始化的(可能是 N+1 )。更方便,更少控制。
更新:有关此主题的更大讨论,请参阅OpenSessionInView 反模式。作者列出了三个要点:
- 每个惰性初始化都会为您提供一个查询,这意味着每个实体将需要 N + 1 个查询,其中 N 是惰性关联的数量。如果你的屏幕显示表格数据,那么阅读 Hibernate 的日志是一个很大的暗示,你不应该做你应该做的事
- 这完全破坏了分层架构,因为您在表示层中使用 DB 弄脏了您的指甲。这是一个概念上的骗局,所以我可以忍受它,但有一个推论
- 最后但同样重要的是,如果在获取会话时发生异常,它将在页面写入期间发生:您无法向用户呈现干净的错误页面,您唯一能做的就是在正文中写入错误消息
事务可以在服务层提交——事务与 OSIV 无关。它Session
保持打开状态,而不是事务 - 运行。
如果您的应用程序层分布在多台机器上,那么您几乎不能使用 OSIV - 您必须在通过网络发送对象之前初始化所需的一切。
OSIV 是一种很好且透明的(即,您的代码都没有意识到它会发生)的方式来利用延迟加载的性能优势
我不会说 Open Session In View 被认为是一种不好的做法。是什么给了你这样的印象?
Open-Session-In-View 是一种使用 Hibernate 处理会话的简单方法。因为它很简单,所以有时很简单。如果您需要对事务进行细粒度控制,例如一个请求中有多个事务,那么 Open-Session-In-View 并不总是一个好方法。
正如其他人所指出的,OSIV 存在一些取舍——您更容易出现 N+1 问题,因为您不太可能意识到您正在启动哪些交易。同时,这意味着您无需更改服务层以适应视图中的微小变化。
如果您使用的是控制反转 (IoC) 容器,例如 Spring,您可能需要阅读bean scoping。本质上,我告诉 Spring 给我一个 HibernateSession
对象,它的生命周期跨越整个请求(即,它在 HTTP 请求的开始和结束时被创建和销毁)。我不必担心LazyLoadException
s 也不必关闭会话,因为 IoC 容器会为我管理它。
如前所述,您将不得不考虑 N+1 SELECT 性能问题。之后,您始终可以配置您的 Hibernate 实体,以便在性能有问题的地方进行即时加入加载。
bean 范围解决方案不是特定于 Spring 的。我知道 PicoContainer 提供了相同的功能,并且我确信其他成熟的 IoC 容器也提供了类似的功能。
根据我自己的经验,OSIV 并没有那么糟糕。我所做的唯一安排是使用两个不同的事务: - 第一个,在“服务层”中打开,我有“业务逻辑” - 第二个在视图渲染之前打开
我刚刚在我的博客中发布了关于何时使用开放会话视图的一些指南。如果您有兴趣,请查看。
http://heapdump.wordpress.com/2010/04/04/should-i-use-open-session-in-view/
这不会有太大帮助,但您可以在这里查看我的主题:* Hibernate Cache1 OutOfMemory with OpenSessionInView
由于 OpenSessionInView 和加载了很多实体,我有一些 OutOfMemory 问题,因为它们停留在 Hibernate 缓存级别 1 并且没有被垃圾收集(我加载了很多实体,每页 500 个项目,但所有实体都留在缓存中)
我对 Hibernate 生疏了.. 但我认为在一个 Hibernate 会话中可能有多个事务。因此,您的事务边界不必与会话开始/停止事件相同。
OSIV,imo,主要是有用的,因为我们可以避免在每次请求需要进行数据库访问时编写启动“持久性上下文”(又名会话)的代码。
在您的服务层中,您可能需要调用具有不同事务需求的方法,例如“必需、新必需等”。这些方法唯一需要的是某人(即 OSIV 过滤器)已经启动了持久性上下文,所以他们唯一需要担心的是——“嘿,给我这个线程的休眠会话。我需要做一些数据库的东西”。