The Open Session in View is a solution to a problem that should not exist in the first place, and the most likely root cause is relying exclusively on entity fetching. If the UI layer only needs a view of the underlying data, then the data access layer is going to perform much better with a DTO projection.
When you don't close your Hibernate sessions and therefore do not release JDBC connections, you have what is typically called Connection leak. So, after a number of requests (depending on the size of your connection pool) the server will not be able to acquire a connection to respond your request.
It all depends on how you obtain the session. if you use sessionFactory. getCurrentSession() , you'll obtain a "current session" which is bound to the lifecycle of the transaction and will be automatically flushed and closed when the transaction ends (commit or rollback).
So, how can i reuse an Hibernate Session, in the same thread, that has been previously closed? Either use the built-in " managed " strategy (set the current_session_context_class property to managed ) or use a custom CurrentSessionContext derived from ThreadLocalSessionContext and override ThreadLocalSessionContet.
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.
OpenSessionInViewFilter
calls the openSession
method of the underlying SessionFactory
and obtains a new Session
.Session
is bound to the TransactionSynchronizationManager
.OpenSessionInViewFilter
calls the doFilter
of the javax.servlet.FilterChain
object reference and the request is further processedDispatcherServlet
is called, and it routes the HTTP request to the underlying PostController
.PostController
calls the PostService
to get a list of Post
entities.PostService
opens a new transaction, and the HibernateTransactionManager
reuses the same Session
that was opened by the OpenSessionInViewFilter
.PostDAO
fetches the list of Post
entities without initializing any lazy association.PostService
commits the underlying transaction, but the Session
is not closed because it was opened externally.DispatcherServlet
starts rendering the UI, which, in turn, navigates the lazy associations and triggers their initialization.OpenSessionInViewFilter
can close the Session
, and the underlying database connection is released as well.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.
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 generated requires 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.
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.
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.
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.
Unfortunately, Open Session in View is enabled by default in Spring Boot.
So, make sure that in the application.properties
configuration file, you have the following entry:
spring.jpa.open-in-view=false
This will disable OSIV, so that you can handle the LazyInitializationException
the right way, by fetching all needed association while the EntityManager
is open.
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.
The view layer is not prepare to handle a HibernateException
which may happen when lazy loading, but presumably the data access layer is.
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.
Update: see The OpenSessionInView antipattern for a larger discussion regarding this subject. The author lists three important points:
- 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
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't use 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
I wouldn't say that Open Session In View is considered a bad practice; what gives you that impression?
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With