Java 为什么 Hibernate Open Session in View 被认为是不好的做法?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/1103363/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Why is Hibernate Open Session in View considered a bad practice?
提问by HeDinges
And what kind of alternative strategies do you use for avoiding LazyLoadExceptions?
您使用什么样的替代策略来避免 LazyLoadExceptions?
I do understand that open session in view has issues with:
我确实了解视图中的开放式会话存在以下问题:
- Layered applications running in different jvm's
- Transactions are committed only at the end, and most probably you would like the results before.
- 在不同的 jvm 中运行的分层应用程序
- 事务只在最后提交,而且很可能你会喜欢之前的结果。
But, if you know that your application is running on a single vm, why not ease your pain by using an open session in view strategy?
但是,如果您知道您的应用程序运行在单个 vm 上,为什么不通过在视图中使用开放会话策略来减轻您的痛苦呢?
回答by Robert Munteanu
Because sending possibly uninitialised Proxies, especially collections, in the view layer and triggering hibernate loading from there can be troubling from both a performance and understanding point of view.
因为在视图层中发送可能未初始化的代理,尤其是集合,并从那里触发休眠加载,从性能和理解的角度来看都会令人不安。
Understanding:
理解:
Using OSIV 'pollutes' the view layer with concerns related to the data access layer.
使用 OSIV 会“污染”与数据访问层相关的问题的视图层。
The view layer is not prepare to handle a HibernateException
which may happen when lazy loading, but presumably the data access layer is.
视图层不准备处理HibernateException
延迟加载时可能发生的情况,但据推测数据访问层是。
Performance:
性能:
OSIV tends to tug proper entity loading under the carpet - you tend not to notice that your collections or entities are lazily initialised ( perhaps N+1 ). More convenience, less control.
OSIV 倾向于在地毯下拖拽适当的实体加载 - 您往往不会注意到您的集合或实体被延迟初始化(可能是 N+1 )。更方便,更少控制。
Update:see The OpenSessionInView antipatternfor a larger discussion regarding this subject. The author lists three important points:
更新:有关此主题的更大讨论,请参阅OpenSessionInView 反模式。作者列举了三个要点:
- each lazy initialization will get you a query meaning each entity will need N + 1 queries, where N is the number of lazy associations. If your screen presents tabular data, reading Hibernate's log is a big hint that you do not do as you should
- this completely defeats layered architecture, since you sully your nails with DB in the presentation layer. This is a conceptual con, so I could live with it but there is a corollary
- last but not least, if an exception occurs while fetching the session, it will occur during the writing of the page: you cannot present a clean error page to the user and the only thing you can do is write an error message in the body
- 每个惰性初始化都会为您提供一个查询,这意味着每个实体将需要 N + 1 个查询,其中 N 是惰性关联的数量。如果你的屏幕显示表格数据,阅读 Hibernate 的日志是一个很大的提示,你没有做你应该做的
- 这完全打败了分层架构,因为你在表现层用 DB 弄脏了你的指甲。这是一个概念上的骗局,所以我可以接受它,但有一个推论
- 最后但并非最不重要的是,如果在获取会话时发生异常,它将在页面写入期间发生:您无法向用户呈现干净的错误页面,您唯一能做的就是在正文中写入错误消息
回答by 0sumgain
If you're using an Inversion of Control (IoC) container such as Spring, you may want to read up on bean scoping. Essentially, I'm telling Spring to give me a Hibernate Session
object whose life cycle spans the entire request (i.e., it gets created and destroyed at the start and end of the HTTP request). I don't have to worry about LazyLoadException
s nor closing the session since the IoC container manages that for me.
如果您使用的是控制反转 (IoC) 容器,例如 Spring,您可能需要阅读bean 范围。本质上,我告诉 Spring 给我一个 HibernateSession
对象,它的生命周期跨越整个请求(即,它在 HTTP 请求的开始和结束时被创建和销毁)。我不必担心LazyLoadException
s 或关闭会话,因为 IoC 容器为我管理。
As mentioned, you will have to think about N+1 SELECT performance issues. You can always configure your Hibernate entity afterwards to do eager join loading in places where performance is an issue.
如前所述,您将不得不考虑 N+1 SELECT 性能问题。之后,您始终可以配置您的 Hibernate 实体,以便在性能有问题的地方进行 Eager join 加载。
The bean scoping solution is not a Spring-specific. I know PicoContainer offers the same capability and I'm sure other mature IoC containers offer something similar.
bean 范围解决方案不是特定于 Spring 的。我知道 PicoContainer 提供了相同的功能,我相信其他成熟的 IoC 容器也提供类似的功能。
回答by Geoffrey Wiseman
I wouldn't say that Open Session In View is considered a bad practice; what gives you that impression?
我不会说 Open Session In View 被认为是一种不好的做法;是什么让你有这样的印象?
Open-Session-In-View is a simple approach to handling sessions with Hibernate. Because it's simple, it's sometimes simplistic. If you need fine-grained control over your transactions, such as having multiple transactions in a request, Open-Session-In-View is not always a good approach.
Open-Session-In-View 是一种使用 Hibernate 处理会话的简单方法。因为它很简单,所以有时很简单。如果您需要对事务进行细粒度控制,例如在一个请求中有多个事务,Open-Session-In-View 并不总是一个好方法。
As others have pointed out, there are some trade-offs to OSIV -- you're much more prone to the N+1 problem because you're less likely to realize what transactions you're kicking off. At the same time, it means you don't need to change your service layer to adapt to minor changes in your view.
正如其他人指出的那样,OSIV 存在一些权衡——您更容易出现 N+1 问题,因为您不太可能意识到您正在启动哪些事务。同时,这意味着您不需要更改您的服务层来适应您视图中的微小变化。
回答by rjk2008
I am v. rusty on Hibernate.. but I think its possible to have multiple transactions in one Hibernate session. So your transaction boundaries do not have to be the same as session start/stop events.
我对 Hibernate 生疏了.. 但我认为在一个 Hibernate 会话中可能有多个事务。因此,您的事务边界不必与会话开始/停止事件相同。
OSIV, imo, primarily is useful because we can avoid writing code for starting a 'persistence context' (a.k.a. session) every time the request needs to make a DB access.
OSIV,imo,主要是有用的,因为我们可以避免每次请求需要进行数据库访问时编写用于启动“持久性上下文”(又名会话)的代码。
In your service layer, you will probably need to make calls to methods which have different transaction needs, such as 'Required, New Required, etc.' The only thing these methods need is that somebody (i.e the OSIV filter) has started up the persistence context, so that only thing they have to worry about is - "hey give me the hibernate session for this thread.. I need to do some DB stuff".
在您的服务层中,您可能需要调用具有不同事务需求的方法,例如“Required、New Required 等”。这些方法唯一需要的是有人(即 OSIV 过滤器)启动了持久化上下文,所以他们唯一需要担心的是 - “嘿,给我这个线程的休眠会话......我需要做一些数据库的东西”。
回答by Chris Upton
I just did a post on some guidelines as to when to use open session in view in my blog. Check it out if your interested.
我刚刚写了一篇关于何时在我的博客中使用开放会话的指南的帖子。如果您有兴趣,请检查一下。
http://heapdump.wordpress.com/2010/04/04/should-i-use-open-session-in-view/
http://heapdump.wordpress.com/2010/04/04/should-i-use-open-session-in-view/
回答by Bozho
transactions can be committed in the service layer - transactions are not related to OSIV. It's the
Session
that stays open, not a transaction - running.if your application layers are spread across multiple machines, then you pretty much can'tuse OSIV - you have to initialize everything you need before sending the object over the wire.
OSIV is a nice and transparent (i.e. - none of your code is aware that it happens) way to make use of the performance benefits of lazy loading
事务可以在服务层提交——事务与 OSIV 无关。它
Session
是保持打开状态,而不是事务 - 正在运行。如果您的应用程序层分布在多台机器上,那么您几乎不能使用 OSIV——您必须在通过网络发送对象之前初始化您需要的一切。
OSIV 是利用延迟加载的性能优势的一种很好且透明的方式(即 - 您的任何代码都不知道它发生了)
回答by Davide
In my own experience, OSIV is not so bad. The only arrangement I made is using two different transactions: - the first, opened in "service layer", where I have the "business logic" - the second opened just before the view rendering
以我自己的经验,OSIV 还不错。我所做的唯一安排是使用两个不同的事务: - 第一个,在“服务层”中打开,我有“业务逻辑” - 第二个在视图渲染之前打开
回答by Sebastien Lorber
This won't help too much but you can check my topic here: * Hibernate Cache1 OutOfMemory with OpenSessionInView
这不会有太大帮助,但您可以在此处查看我的主题:* Hibernate Cache1 OutOfMemory with OpenSessionInView
I have some OutOfMemory issues because of OpenSessionInView and a lot of entities loaded, because they stay in Hibernate cache level1 and are not garbage collected (i load a lot of entities with 500 items per page, but all entities stay in cache)
我有一些 OutOfMemory 问题,因为 OpenSessionInView 和加载了很多实体,因为它们停留在 Hibernate 缓存级别 1 并且没有被垃圾收集(我加载了很多实体,每页有 500 个项目,但所有实体都保留在缓存中)
回答by Vlad Mihalcea
For a longer description, you can read my Open Session In View Anti-Patternarticle. Otherwise, here's a summary for why you shouldn't use Open Session In View.
如需更详细的描述,您可以阅读我的Open Session In View Anti-Pattern文章。否则,这里是为什么不应该使用 Open Session In View 的摘要。
Open Session In View takes a bad approach to fetching data. Instead of letting the business layer decide how it's best to fetch all the associations that are needed by the View layer, it forces the Persistence Context to stay open so that the View layer can trigger the Proxy initialization.
在视图中打开会话采用了一种糟糕的方法来获取数据。它不是让业务层决定如何最好地获取视图层所需的所有关联,而是强制持久化上下文保持打开状态,以便视图层可以触发代理初始化。
- The
OpenSessionInViewFilter
calls theopenSession
method of the underlyingSessionFactory
and obtains a newSession
. - The
Session
is bound to theTransactionSynchronizationManager
. - The
OpenSessionInViewFilter
calls thedoFilter
of thejavax.servlet.FilterChain
object reference and the request is further processed - The
DispatcherServlet
is called, and it routes the HTTP request to the underlyingPostController
. - The
PostController
calls thePostService
to get a list ofPost
entities. - The
PostService
opens a new transaction, and theHibernateTransactionManager
reuses the sameSession
that was opened by theOpenSessionInViewFilter
. - The
PostDAO
fetches the list ofPost
entities without initializing any lazy association. - The
PostService
commits the underlying transaction, but theSession
is not closed because it was opened externally. - The
DispatcherServlet
starts rendering the UI, which, in turn, navigates the lazy associations and triggers their initialization. - The
OpenSessionInViewFilter
can close theSession
, and the underlying database connection is released as well.
- 该
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
,和底层数据库连接被释放为好。
At a first glance, this might not look like a terrible thing to do, but, once you view it from a database perspective, a series of flaws start to become more obvious.
乍一看,这似乎不是一件可怕的事情,但是,一旦从数据库的角度来看,一系列缺陷就开始变得更加明显。
The service layer opens and closes a database transaction, but afterward, there is no explicit transaction going on. For this reason, every additional statement issued from the UI rendering phase is executed in auto-commit mode. Auto-commit puts pressure on the database server because each statement must flush the transaction log to disk, therefore causing a lot of I/O traffic on the database side. One optimization would be to mark the Connection
as read-only which would allow the database server to avoid writing to the transaction log.
服务层打开和关闭一个数据库事务,但之后没有显式事务在进行。出于这个原因,从 UI 呈现阶段发出的每个附加语句都在自动提交模式下执行。自动提交给数据库服务器带来压力,因为每个语句都必须将事务日志刷新到磁盘,从而导致数据库端的大量 I/O 流量。一种优化是将 标记Connection
为只读,这将允许数据库服务器避免写入事务日志。
There is no separation of concerns anymore because statements are generated both by the service layer and by the UI rendering process. Writing integration tests that assert the number of statements being generatedrequires going through all layers (web, service, DAO), while having the application deployed on a web container. Even when using an in-memory database (e.g. HSQLDB) and a lightweight web server (e.g. Jetty), these integration tests are going to be slower to execute than if layers were separated and the back-end integration tests used the database, while the front-end integration tests were mocking the service layer altogether.
不再有关注点分离,因为语句是由服务层和 UI 渲染过程生成的。编写断言正在生成的语句数量的集成测试需要遍历所有层(Web、服务、DAO),同时将应用程序部署在 Web 容器上。即使在使用内存数据库(例如 HSQLDB)和轻量级 Web 服务器(例如 Jetty)时,这些集成测试的执行速度也会比层分离并且后端集成测试使用数据库要慢,而前端集成测试完全模拟了服务层。
The UI layer is limited to navigating associations which can, in turn, trigger N+1 query problems. Although Hibernate offers @BatchSize
for fetching associations in batches, and FetchMode.SUBSELECT
to cope with this scenario, the annotations are affecting the default fetch plan, so they get applied to every business use case. For this reason, a data access layer query is much more suitable because it can be tailored for the current use case data fetch requirements.
UI 层仅限于导航关联,这反过来又会触发 N+1 查询问题。尽管 Hibernate 提供@BatchSize
批量获取关联,并且FetchMode.SUBSELECT
为了应对这种情况,注释会影响默认获取计划,因此它们被应用于每个业务用例。出于这个原因,数据访问层查询更合适,因为它可以针对当前用例数据获取需求进行定制。
Last but not least, the database connection could be held throughout the UI rendering phase(depending on your connection release mode) which increases connection lease time and limits the overall transaction throughput due to congestion on the database connection pool. The more the connection is held, the more other concurrent requests are going to wait to get a connection from the pool.
最后但并非最不重要的是,数据库连接可以在整个 UI 呈现阶段(取决于您的连接释放模式)保持,这会增加连接租用时间并由于数据库连接池拥塞而限制整体事务吞吐量。保持的连接越多,其他并发请求将等待从池中获取连接的次数就越多。
So, either you get the connection held for too long, either you acquire/release multiple connections for a single HTTP request, therefore putting pressure on the underlying connection pool and limiting scalability.
因此,要么您将连接保持的时间过长,要么为单个 HTTP 请求获取/释放多个连接,从而给底层连接池带来压力并限制可扩展性。
Spring Boot
弹簧靴
Unfortunately, Open Session in View is enabled by default in Spring Boot.
不幸的是,在 Spring Boot 中默认启用 Open Session in View。
So, make sure that in the application.properties
configuration file, you have the following entry:
因此,请确保在application.properties
配置文件中包含以下条目:
spring.jpa.open-in-view=false
This will disable OSIV, so that you can handle the LazyInitializationException
the right way.
这将禁用OSIV,这样就可以处理LazyInitializationException
的正确方法。