问题

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

我明白在视图中打开会话有以下问题:

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

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


#1 热门回答(40 赞)

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

理解:

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

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

性能:

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

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

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


#2 热门回答(28 赞)

有关详细说明,请参阅myOpen Session In View Anti-Patternarticle。否则,以下是你不应在视图中使用Open Session的原因摘要。

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

enter image description here

  • OpenSessionInViewFilter调用底层SessionFactory的openSession方法并获取新的Session。
  • 会话绑定到TransactionSynchronizationManager。
  • OpenSessionInViewFilter调用javax.servlet.FilterChain对象引用的doFilter,并进一步处理请求
  • 调用DispatcherServlet,它将HTTP请求路由到底层的PostController。
  • PostController调用PostService获取Post实体列表。
  • PostService打开一个新事务,HibernateTransactionManager重用OpenSessionInViewFilter打开的同一个Session。
  • PostDAO在不初始化任何延迟关联的情况下获取Post实体列表。
  • PostService提交基础事务,但会话未关闭,因为它是在外部打开的。
  • DispatcherServlet开始呈现UI,而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 is enabled by default in Spring Boot

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

spring.jpa.open-in-view=false

这将禁用OSIV,以便你可以正确地处理LazyInitializationException


#3 热门回答(23 赞)

  • 可以在服务层中提交事务 - 事务与OSIV无关。这是会话保持开放,而不是交易 - 运行。
  • 如果你的应用程序层分布在多台机器上,那么你几乎无法使用OSIV - 你必须在通过网络发送对象之前初始化你需要的所有内容。
  • OSIV是一个很好的透明(即 - 你的代码都没有意识到它发生了)的方式来利用延迟加载的性能优势

原文链接