This article introduces an approach to preload certain relations in complex object graphs with Hibernate on a per-usecase basis. The intention is to prevent
LazyInitializationException during runtime and to reduce the N+1 SELECT problem while working with lazy relations. What does per-usecase mean in this context? The approach affords to easily decide which parts of an object graph are directly loaded by Hibernate for each and every usecase . If you're familiar with these problems you can skip the next section and dive directly into the proposed pattern.
Do you know Hibernates
LazyInitializationException? It's one of the annoying parts of the object-relational mapper. So, when does Hibernate throw this exception? Think of an entity A with a one-to-many relation to an entity B. Per default this relation is marked as lazy. What does this mean? If you load objects of type A the relational objects of type B will not be loaded. Instead Hibernate uses his own Collection implementations (e.g.
PersistentList). Internally there is a Hibernate session bound to these collections. This allows Hibernate to load the collection of B objects at the first time you're accessing the collection. This works perfectly well as long as the Hibernate session bound to the collection is open. Per default the session closes automatically on the transactions commit. As a consequence a LazyInitializationException will be thrown if you try to access the collection after the transaction has been commited. Hibernate objects not bound to an active session are called detached objects.
The N+1 SELECT Problem
You may ask yourself: What if I never use detached objects? Indeed this is a possible solution to prevent LazyInitalizationExceptions to be thrown. But another problem arises: when you first access a non-initialized (lazy) collection Hibernate loads the objects by querying the database with additional SELECT statements. Think of a complex object graph where entity A knows many B entities which knows many C entities and so on. As you can imagine maaaaany SELECTS are fired while traversing this object graph (from A to C, back and forth). This leads to performance issues and is called the N+1 SELECT problem.
The Preload Pattern
So what might be the simpliest solution to prevent both LazyInitializationExceptions on detached objects and the N+1 SELECT problem? You're right: we have to minimize the use of lazy initialized collections. And we want to do this on a per-usecase basis so we can individually decide for each usecase which data will be preloaded.
Let me introduce to you the so called
CriteriaJoiner. The class allows you to easily specify which paths of an object graph you want to preload. Alternatively the class creates a Criteria or DetachedCriteria object automatically. Internally the created criteria uses LEFT JOINs to preload the demanded object graph with just one SELECT statement. It's up to you to modify the created criteria by adding additional restrictions.
The Usage of CriteriaJoiner
The CriteriaJoiner will be instantiated for a given mapped hibernate class using the appropriate static methods. Then you can specify which part of the object graph you want to preload. This is done by adding additional paths based on the given root class. What does path mean in this context? A path is the concatenation of one or more collection member names separated by a slash, forming together a path through the graph of objects. So, adding the path a assumes that there is a property collection of name a in the specified root class. Adding the path a/b additionally assumes that the class for a has a property collection of name b and so on. After adding all paths you can create the criteria or detached criteria object for querying the database. Additionally you can use the Preload enum (see below) to further restrict the preload depth. This allows you to re-use certain join-criterias with different fetching depths for different usecases.
The Source Code
Additionally to the source code below you need to setup a project with Hibernate 3 on classpath:
The introduced CriteriaJoiner is a convient solution to prevent LazyInitializationExceptions and the N+1 SELECT problem. Its flexibility allows you to decide for each usecase which data you want to be loaded by Hibernate. The class creates criteria or detached criteria objects which internally use LEFT JOINs to fetch all properties with just one SELECT statement.There're some known limitations on this approach. Because CriteriaJoiner creates aliases for each property given by the paths it's difficult to use these aliases in restrictions you might add to the criteria. This issue could be solved by introducing some kind of naming convention for the created aliases so you could re-use those aliases in the WHERE clause. There is another limitation while using this approach in combination with pagination. This is due to the fact that the result set of such FETCH JOIN statements contains multiple rows for the same entity. Therefor Hibernate cannot generate pageable SQL statements. In that case pagination would be done in-memory which can cause performance issues.