Spring's Open-Session-in-View & Transactions

Written on , by Andrew Lalis.

If you're reading this article, there's a good chance you've already heard about Spring's controversial OpenSessionInViewFilter, which allows lazy loading of entity relations during the rendering of a web view. In case you haven't, I suggest reading this excellent article by Vlad Mihalcea which goes into much more detail on why one should avoid using the filter entirely.

This article will focus on the practical way to structure your application logic so that you can still take advantage of Hibernate's lazy loading, without using the OSIV anti-pattern.

Don't Send Entities to Your View

At first, many of the simple tutorials you'll read online will have you passing your entity models to Thymeleaf or JSP templates to render a web page for your application. This convenience is what leads developers to use OSIV in the first place, because they start fetching attributes of their entity which were not initially loaded. This is fine for the most simple "Hello World" starter projects, but as soon as you add any real complexity, this "convenience" breaks down into an unorganized mess, where it is not clear at a glance where data will be fetched from the database.

One solution, which beginners often turn to, is to simply eager-fetch all the attributes they'll need using a their data-access object (usually a repository with a custom JPQL query). This works well enough, and is often the preferred solution since it's about as optimized as you can get. The issue with this approach is that it's just not scalable to maintain a huge library of queries that are each tailor-made for a particular use case, and tends to sidestep Hibernate's powerful lazy-fetching features.


					@Query("SELECT a FROM Assignment a " +
							"LEFT JOIN FETCH a.gradings " +
							"LEFT JOIN FETCH a.zippedFile " +
							"WHERE a.id = :id")
					Optional<Assignment> findByIdWithAllRelations(Long id);
			
This is an example of the the sort of JPQL queries that will become common if you try to always fetch exactly what you need. One or two of these methods is fine, but if the Assignment entity is used in 150 different places in our code, each with different requirements for which attribute need to be fetched, then this becomes unsustainable.

Eager-fetching the right attributes for an entity still leaves you with the issue of passing your actual entity object to the view for rendering, which means that an unsuspecting developer will eventually try to read a lazy-loaded attribute, which will throw a LazyInitializationException. The solution to that issue, is to not pass your entity to the view at all, but instead pass a data-transfer object (DTO) containing exactly the data that the view requires, and nothing more. And now, your service layer is responsible for producing this DTO, and it can take full advantage of Hibernate's lazy-loading functionality.

Transactions

The Spring framework allows you to annotate a service's method as @Transactional, which means that at runtime, when your method is called, a proxy method is wrapped around it which starts a transaction (and thus a Hibernate session to go with it) that persists for the duration of the method. Inside this method, you are free to fetch lazy-loaded attributes from an entity, without having to deal with LazyInitializationExceptions.


				@Transactional
				public boolean isPersonAdmin(long personId) {
					Person p = this.personRepo.findById(personId).orElseThrow();
					for (Role r : p.getRoles()) {
						if (r.getName().equals("ADMIN")) return true;
					}
					return false;
				}
			
In this example, we have a service method isPersonAdmin, which looks to see if a person has the "ADMIN" role. This will work even if the person's list of roles is lazy-loaded, because it all happens in the context of the transaction.

One caveat to be aware of is the fact that entity object should not be passed as arguments to a transactional method, since the Hibernate session that is dedicated to the transaction has no idea what that entity is, and thus can't fetch lazy attributes from it.

However, the flexibility of being able to use lazy-loaded attributes the easy way more than makes up for the strict encapsulation rules, and by structuring your project around service methods as black-box operations, you'll be able to build huge projects while keeping your sanity mostly intact.

Back to Articles