OSIV 反模式
OSIV(在视图中打开会话)不是让业务层决定如何最好地获取视图层所需的所有关联,而是强制持久性上下文保持打开状态,以便视图层可以触发代理初始化,如图所示通过下图。
- The
OpenSessionInViewFilter
称为openSession
底层方法SessionFactory
并获得一个新的Session
.
- The
Session
绑定到TransactionSynchronizationManager http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/transaction/support/TransactionSynchronizationManager.html.
- The
OpenSessionInViewFilter
称为doFilter
of the javax.servlet.FilterChain
对象引用并进一步处理请求
- The DispatcherServlet http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/servlet/DispatcherServlet.html被调用,它将 HTTP 请求路由到底层
PostController
.
- The
PostController
称为PostService
得到一个列表Post
实体。
- The
PostService
打开一个新交易,并且HibernateTransactionManager
重复使用相同的Session
这是由OpenSessionInViewFilter
.
- The
PostDAO
获取列表Post
实体而不初始化任何惰性关联。
- The
PostService
提交基础事务,但是Session
没有关闭,因为它是从外部打开的。
- The
DispatcherServlet
开始渲染 UI,而 UI 又会导航惰性关联并触发它们的初始化。
- The
OpenSessionInViewFilter
可以关闭Session
,并且底层数据库连接也被释放。
乍一看,这可能看起来并不是一件可怕的事情,但是,一旦您从数据库的角度来看它,一系列缺陷就会开始变得更加明显。
服务层打开和关闭数据库事务,但之后没有显式事务发生。因此,从 UI 渲染阶段发出的每个附加语句都以自动提交模式执行。自动提交会给数据库服务器带来压力,因为每个事务在结束时都会发出提交,这可能会触发事务日志刷新到磁盘。一种优化是标记Connection
作为只读,这将允许数据库服务器避免写入事务日志。
不再存在关注点分离,因为语句是由服务层和 UI 渲染过程生成的。编写断言生成的语句数量的集成测试需要遍历所有层(Web、服务、DAO),同时将应用程序部署在 Web 容器上。即使使用内存数据库(例如 HSQLDB)和轻量级 Web 服务器(例如 Jetty),这些集成测试的执行速度也会比层分离并且后端集成测试使用数据库而前端集成测试要慢。最终集成测试完全模拟了服务层。
UI 层仅限于导航关联,这反过来又会触发 N+1 查询问题。尽管 Hibernate 提供@BatchSize https://docs.jboss.org/hibernate/orm/current/javadocs/org/hibernate/annotations/BatchSize.html用于批量获取关联,以及FetchMode.SUBSELECT https://docs.jboss.org/hibernate/orm/current/javadocs/org/hibernate/annotations/FetchMode.html#SUBSELECT为了应对这种情况,注释会影响默认的获取计划,因此它们会应用于每个业务用例。因此,数据访问层查询更合适,因为它可以根据当前用例的数据获取要求进行定制。
最后但并非最不重要的一点是,数据库连接在整个 UI 呈现阶段保持不变,这会增加连接租用时间,并由于数据库连接池拥塞而限制总体事务吞吐量。持有的连接越多,等待从池中获取连接的其他并发请求就越多。
Spring Boot 和 OSIV
很遗憾,Spring Boot 中默认启用 OSIV(在视图中打开会话) https://github.com/spring-projects/spring-boot/issues/7107,从性能和可扩展性的角度来看,OSIV 确实是一个坏主意。
因此,请确保在application.properties
配置文件中,您有以下条目:
spring.jpa.open-in-view=false
这将禁用 OSIV,以便您可以处理LazyInitializationException
正确的方式。
从2.0版本开始,Spring Boot出现问题OSIV 时发出警告 https://github.com/spring-projects/spring-boot/issues/7107默认情况下启用,因此您可以在问题影响生产系统之前就发现它。