你采用什么样的备选策略来避免LazyLoadExceptions呢?
我知道在视图中开启会话存在以下问题:
- 运行在不同jvm的分层应用程序
- 事务只有在最后才提交,而你很可能希望在此之前获得结果。
但是,如果你知道你的应用程序在单个VM上运行,为什么不使用一个在视图中开启会话的策略来减轻痛苦呢?
你采用什么样的备选策略来避免LazyLoadExceptions呢?
我知道在视图中开启会话存在以下问题:
但是,如果你知道你的应用程序在单个VM上运行,为什么不使用一个在视图中开启会话的策略来减轻痛苦呢?
在视图中打开会话采用了一种不好的数据获取方法。它强制持久化上下文保持打开状态,以便视图层可以触发代理初始化,而不是让业务层决定如何最好地获取视图层所需的所有关联。
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
,底层数据库连接也将被释放。@BatchSize
批量获取关联和FetchMode.SUBSELECT
处理此场景,但注释会影响默认获取计划,因此它们将应用于每个业务用例。因此,数据访问层查询更加适合,因为它可以根据当前用例数据获取要求进行定制。不幸的是,在Spring Boot中默认启用了Open Session in View。
因此,请确保在application.properties
配置文件中有以下条目:
spring.jpa.open-in-view=false
LazyInitializationException
,通过在EntityManager
打开时获取所有所需的关联。请注意保留HTML标签。因为在视图层中发送可能未初始化的代理对象,特别是集合对象,会从性能和理解角度带来麻烦。
理解:
使用OSIV会将与数据访问层相关的问题“污染”到视图层。
视图层不能处理可能发生的HibernateException
(如延迟加载),但数据访问层可以。
性能:
OSIV倾向于把适当的实体加载隐藏起来-您往往不会注意到您的集合或实体已被延迟初始化(也许是N+1)。更方便,但控制力较弱。
更新:请参阅The OpenSessionInView反模式,以获取有关此主题的更大讨论。作者列出了三个重要观点:
- 每次延迟初始化都会生成一个查询,这意味着每个实体将需要N + 1个查询,其中N是延迟关联的数量。如果您的屏幕显示表格数据,则读取Hibernate日志表明您没有按照正确的方式操作。
- 这完全破坏了分层架构,因为您在表示层中会与数据库交互。这是一个概念上的问题,所以我可以接受,但有个附带条件
- 最后但并非最不重要的,如果在获取会话时发生异常,则将在编写页面时发生异常:您无法向用户呈现干净的错误页面,唯一能做的就是在页面正文中编写错误消息。
可以在服务层提交事务 - 事务与OSIV无关。它是Session
保持开启,而不是事务 - 运行。
如果您的应用程序层分布在多台计算机上,则基本上无法使用OSIV - 您必须在将对象发送到远程之前初始化所需的所有内容。
OSIV是一种不错且透明的方式(即 - 您的代码不知道这是发生了)来利用延迟加载的性能优势。
我不认为Open Session In View被视为一种不良实践;是什么给你留下了这样的印象?
开放式会话视图是处理Hibernate会话的一种简单方法。因为它很简单,所以有时候过于简单化。如果您需要对事务进行细粒度控制,例如在一个请求中有多个事务,那么开放式会话视图并不总是一个好的选择。
正如其他人所指出的,OSIV存在一些权衡取舍--您更容易遇到N+1问题,因为您不太可能意识到您正在启动哪些事务。与此同时,这意味着您无需更改服务层来适应视图中的小变化。
Session
对象,其生命周期跨越整个请求(即,在HTTP请求的开始和结束时创建和销毁)。我不必担心LazyLoadException
或关闭会话,因为IoC容器为我管理它们。根据我的经验,OSIV并不是那么糟糕。 我所做的唯一安排是使用两个不同的事务: - 第一个在“服务层”中打开,在这里我有“业务逻辑” - 第二个在视图渲染之前打开。
我刚刚在我的博客中发布了一些有关何时使用open session in view的指南。如果你感兴趣,请查看以下链接。
http://heapdump.wordpress.com/2010/04/04/should-i-use-open-session-in-view/
我对Hibernate有点生疏,但我认为在一个Hibernate会话中可以有多个事务。因此,您的事务边界不必与会话的开始/停止事件相同。
在我看来,OSIV主要是有用的,因为我们可以避免每次请求需要进行数据库访问时编写启动“持久化上下文”(即会话)的代码。
在您的服务层中,您可能需要调用具有不同事务需求的方法,例如“Required, New Required”等。这些方法唯一需要的是某人(即OSIV过滤器)已经启动了持久化上下文,以便他们所关心的只是 - “嘿,给我这个线程的Hibernate会话..我需要做一些数据库操作。”
这并不能太有帮助,但你可以在这里查看我的主题: * Hibernate Cache1 OutOfMemory with OpenSessionInView
由于OpenSessionInView和大量加载的实体,我遇到了一些OutOfMemory问题,因为它们停留在Hibernate缓存级别1中,并且没有被垃圾回收(我每页加载500个项目,但所有实体都停留在缓存中)。
太长不看
之前的回答对于我理解真正实际避免使用OSIV的原因来说有些不方便。
OSIV是一种反模式,那么实际常见的原因是什么呢?
OSIV反模式是指在整个请求-响应周期中保持数据库会话处于打开状态(在大多数情况下,人们使用基于线程的每个请求模型),这意味着会话在请求生命周期期间保持打开状态。 OSIV的目的是允许在视图中进行实体关联的延迟加载,并在需要访问延迟加载实体时按需使用会话。