The Open Session In View Anti-Pattern - Vlad Mihalcea
Introduction
The Open Session in View is an Anti-Pattern, and this post is going to demonstrate why it is so. First of all, let’s start with the Wikipedia definition of an Anti-Pattern:
An anti-pattern (or antipattern) is a common response to a recurring problem that is usually ineffective and risks being highly counterproductive.
A LazyInitializationException band aid
When using JPA and Hibernate, the Fetching policy can have one of the biggest impacts on application performance, and, as explained in my High-Performance JDBC presentation, you should always fetch just as much data you need to fulfil the requirements of a given business logic use case. Fetching too many columns than necessary has an impact, and that’s why entities are not good candidates for read-only views. In turn, DTO projectionsare better suited for read-only data sets.
Entities are very useful for read-write transactions because you can benefit from the automatic dirty checking mechanismwhile preventing lost updates phenomenain multi-request logical transactions.
Unfortunately, many enterprise applications don’t make this distinction, and they rely solely on entities for both read-only and read-write transactions. Not only that an entity has more columns than a custom DTO projection, but the entity might have associations as well. Entity associations are convenient because it allows the application developer to access joined relationships without even needing to write a query.
Hibernate comes with Proxies that allow the application developer to defer fetching until the association is needed. This is very useful, especially from a performance perspective. The worst thing to do is to use EAGER associationsbecause, once a relationship is set to be eagerly fetched, it cannot be changed to being fetched lazily on a per query basis. For this reason, many associations are configured with the FetchType.LAZY
attribute.
However, a LAZY association needs the Session
to be opened in order to initialize the Proxy. If the Persistence Context is closed, when trying to access a non-initialized LAZY association, the infamous LazyInitializationException
is thrown.
For read-only views, when using DTO projections, we have to manually choose the child associations properties too, therefore, the LazyInitializationException
cannot occur. For read-write transactions, entities might be fetched with the intention of being modified and saved at the end of the currently running workflow. These entities are prone to LazyInitializationException(s)
, so there are good ways and bad ways of dealing with this issue.
It is only the business layer responsibility to fetch all the data that’s necessary for a particular business use case. For many-to-one and one-to-one associations, as well as to at most one one-to-many
relationship, JOIN FETCH
directive is the best way of initializing the associations that are going to be needed in the view layer. For multiple one-to-many
associations, to avoid a Cartesian Product, it’s necessary to use secondary queries. These secondary queries can be fired when the association is accessed for the first time, which can be done with the Hibernate.initialize(proxy)
utility.
Open Session In View takes a different approach. 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.
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 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.
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 to the current use case data fetch requirements.
Last but not least, the database connection is held throughout the UI rendering phase 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.
Read-life stories
- Spring Boot: Open Session In View caused cache problems
- Paper scalability, Bcrypt, and Zonky’s performance debugging (Czech)
After a long debate, it’s good that Spring Boot issues a warning id the Open Session In View mode is active.
If you enjoyed this article, I bet you are going to love my Bookand Video Coursesas well.
Conclusion
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.
A DTO projectionforces the application developer to fetch just the required data set and is not susceptible to LazyInitializationException(s)
. This way, the separation of concerns is no longer compromised, and performance optimizations can be applied at the data access layer level since all statements are confined to the boundaries of the currently executing transaction.