这个"spring.jpa.open-in-view=true"属性在Spring Boot中是什么意思?

283

我在Spring Boot文档中看到了spring.jpa.open-in-view=true属性,它与JPA配置相关。

  • 如果没有提供此属性,true是否是默认值?
  • 这个属性实际上是做什么的?我没有找到任何好的解释。
  • 它是否使您使用SessionFactory而不是EntityManagerFactory?如果是,我该如何告诉它允许我使用EntityManagerFactory

谢谢!

4个回答

568

OSIV反模式

不要让业务层决定如何获取视图层所需的所有关联信息,而是使用OSIV(在视图中打开会话)强制持久性上下文保持打开状态,以便视图层可以触发代理初始化,如下图所示。

OSIV Anti-Pattern


  • OpenSessionInViewFilter 调用底层 SessionFactoryopenSession 方法,获得一个新的 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渲染阶段发出的每个附加语句都会在自动提交模式下执行。自动提交会给数据库服务器带来压力,因为每个事务都在结束时发出提交,这可能会触发事务日志刷新到磁盘。一个优化是将Connection标记为只读,这将允许数据库服务器避免写入事务日志。
现在已经没有关注点分离了,因为语句既由服务层生成,也由UI渲染过程生成。编写断言生成的语句数量的集成测试需要通过所有层(Web、服务、DAO),同时应用程序部署在Web容器上。即使使用内存数据库(例如HSQLDB)和轻量级Web服务器(例如Jetty),这些集成测试的执行速度也会比分离层并且后端集成测试使用数据库,而前端集成测试完全模拟服务层要慢。

UI层仅限于导航关联,这可能会触发N+1查询问题。虽然Hibernate提供了@BatchSize以批处理方式获取关联数据,FetchMode.SUBSELECT可应对此情况,但这些注释会影响默认的获取计划,因此会应用于每个业务用例。出于这个原因,数据访问层查询更加合适,因为它可以根据当前用例数据获取要求进行定制。

最后,数据库连接在UI渲染阶段被保持,这增加了连接租约时间,并由于数据库连接池拥塞而限制了整体事务吞吐量。连接被保持的时间越长,其他并发请求等待从池中获得连接的时间就越长。

Spring Boot和OSIV

不幸的是,在Spring Boot中默认启用OSIV(Open Session in View), 从性能和可伸缩性的角度来看,OSIV确实是一个糟糕的想法。

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

spring.jpa.open-in-view=false

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

从2.0版本开始,Spring Boot默认启用OSIV时会发出警告,因此您可以在影响生产系统之前发现此问题。


39
现在有一个警告正在记录。 - Vlad Mihalcea
2
这不是反模式。它确实会对性能产生影响,有时是负面的,很多时候相当中立,在许多情况下以积极的方式:如果你实际上想要一个懒惰的关系,你不需要在所有情况下进行查询,并且可以通过使用open-in-view在需要时避免它。 - ymajoros
3
如果您决定添加 "spring.jpa.open-in-view=false",请确保正确重启您的 Spring Boot 应用程序,而不仅仅是让它自动重新加载。如果您选择后者,您将收到一个已存在的 "open-in-view @ConditionalOnProperty" 的通知。请参阅 https://www.yawintutor.com/conditionalonproperty-found-different-open-in-view/。在我的情况下,手动重启服务器有所帮助。 - Igor
6
完全不信服。就我个人而言,我并不是很喜欢OSIV,但这里给出的理由纯属胡扯。1)“自动提交会加大数据库服务器压力,因为每个语句都必须将事务日志刷新到磁盘,从而在数据库端引起大量I/O流量。”这不对,关系型数据库管理系统并不是那么愚蠢——这种说法没有任何证据支持,而且根据经验我知道它并不会发生。2)“现在不再有责任分离”,这是无稽之谈,因为那些JDBC语句是透明和隐式生成的;... - Rogério
10
  1. 当然,你是错误的。每个事务都会发出一个提交指令,由数据库进行处理。因此,在2PL中,读锁必须释放,而在MVCC中,SI快照可以被丢弃。这是数据库基础知识。
  2. 再次,你错了。JDBC与此无关。这是关于哪个层控制事务边界的问题。
  3. 再次错误。N+1可能通过OSIV或FetchType.EAGER发生,而不仅仅是懒加载集合。
  4. 再次错误。这个结论是通过通用可伸缩性定律数学证明的。你也应该阅读一下 ;)
- Vlad Mihalcea
显示剩余7条评论

94

这个属性将注册一个OpenEntityManagerInViewInterceptor,它会将一个EntityManager注册到当前线程中,因此在Web请求结束之前,您将拥有相同的EntityManager。这与Hibernate的SessionFactory无关。


目前我使用OpenEntityManagerInViewFilter过滤器来控制EntityManager直到Web请求完成。你所说的拦截器“OpenEntityManagerInViewInterceptor”是指与“OpenEntityManagerInViewFilter”相同的东西吗?它们之间有什么区别?那么,在Spring Boot中,我就不需要再使用这个过滤器了吗? - Carlos Alberto
2
拦截器只在使用Spring的DispatcherServlet时才有效(因为拦截器是Spring机制)。过滤器可以映射到所有配置的servlet(我们在其中一个应用程序中使用它来处理FacesServlet)。因此,如果您仅使用DispatcherServlet,则可以添加属性并删除过滤器;否则,请使用过滤器。 - dunni

22

可能有点晚了,但是我正在尝试更深入地了解关闭与打开的影响,我发现这篇文章很有用 spring-open-session-in-view

希望这能帮助到某些人......


1
开放式视图(Open-in-View,简称OSIV)的一个原因是为了提高开发人员的生产力,因为它消除了显式加载所有延迟加载属性的需要。 https://dev59.com/RmIj5IYBdhLWcg3wv3m2#76864391展示了一种进行显式加载的方法。通过关闭open-in-view以防止LazyInitializationException的出现。
然而,这会导致每个执行JPA操作的控制器映射都启动一个事务,从而可能导致数据库连接耗尽。
另一种方式(也被认为是一种反模式)是告诉Hibernate在没有事务的情况下允许延迟加载。可以通过添加相应的配置来实现。
spring:
  jpa:
    properties:
      hibernate.enable_lazy_load_no_trans: true

就开发人员的生产力而言,这是相同的,但在性能方面可能会稍微好一些,因为只有在需要延迟加载对象之后才会启动事务。
显然,正确的方法是使用join fetch@EntityGraph,但这会增加开发人员的复杂性。但这可以在之后提高性能。
在我看来,使用hibernate.enable_lazy_load_no_trans并关闭open-in-view,但找到触发enable_lazy_load_no_trans功能的地方(最好通过日志链接)并进行修正。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接