The Open Session In View Anti-Pattern
Imagine having a tool that can automatically detect JPA and Hibernate performance issues. Hypersistence Optimizer is that tool!
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 projections are better suited for read-only data sets.
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 associations because, 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
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
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
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.
openSessionmethod of the underlying
SessionFactoryand obtains a new
Sessionis bound to the
javax.servlet.FilterChainobject reference and the request is further processed
DispatcherServletis called, and it routes the HTTP request to the underlying
PostServiceto get a list of
PostServiceopens a new transaction, and the
HibernateTransactionManagerreuses the same
Sessionthat was opened by the
PostDAOfetches the list of
Postentities without initializing any lazy association.
PostServicecommits the underlying transaction, but the
Sessionis not closed because it was opened externally.
DispatcherServletstarts rendering the UI, which, in turn, navigates the lazy associations and triggers their initialization.
OpenSessionInViewFiltercan 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 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.
- Spring Boot: Open Session In View caused cache problems
- Spring Boot Best Practice – Disable OSIV to start receiving LazyInitializationException warnings again
- 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.
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 projection forces 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.