What is LazyInitializationException?
I’m going to go out on a limb and say: Anybody who has ever used Hibernate has at some point triggered a LazyInitializationException. Seriously, questions about what this exception is and how to fix it pop up all the time.
What
is a LazyInitializationException? In a nutshell, if you use Hibernate
to load an entity from the database, and then try to access a
lazily-loaded portion of that entity’s object graph from outside
the database session, Hibernate will throw a
LazyInitializationException. The simple answer is to just not access
lazily loaded properties or collections outside of a session. However,
the devil, as they say, is in the details.
If you’re unfamiliar with this exception and its solutions, I recommend reading up on the many options to solve
this problem. The options have been covered enough and are google-able
enough that I don’t need to discuss all of them here (except maybe to
ask you to avoid OpenSessionInView). But what I can discuss here is my own solution to the problem, and
show how my solution provides a two-for-one bundle of goodness.
What were the issues I was looking to solve?
Besides preventing this exception, first of all I was looking for a solution that would convert Hibernate objects into a DTO that looks the same as my regular entity / domain object. I investigated mapping with Dozer,
but it looked a bit heavy for my needs, and I didn’t want to introduce
another library unless it was actually necessary. Another option is to
use a HibernateUnproxifier, but that still requires code to access each specific collection every time, and I was looking for something more general.
Secondly,
I was looking for a solution that would load collections to the right
depth (deep enough, or shallow enough). Sometimes I wanted just the
top-level properties of an object, sometimes I wanted the object’s
objects or collections, and so on. As mentioned above, the simple answer
is that you can just lazily load the objects and collections you want
as needed. But the devil in the details is that it quickly becomes
cumbersome to modify your DAO or Service with methods to load this
specific collection here and that specific collection there.
Finally,
I was looking for a solution that prevents circular references.
Sometimes my domain classes had to have circular references to satisfy
certain Hibernate mappings. When marshalling these objects to JSON, the
conversion would break. It’s easy to introduce @JsonBackReference, but again I felt like there had to be a better way.
Copy It!
Enter the VariableDepthCopier.
With this class you can copy an object coming from the database into a
new instance of the same class, and specify how deep the resulting
object graph should be in the new copy.
This copy can then be safely passed around or marshalled to the client
without worrying about LazyInitializationExceptions.
As for how to specify the copy depth: an
object copied with level 0 has just the primitives and java.lang
immutable classes (such as String, Number, Date, etc). Non-primitive
properties will be set to null, and collections will be empty. Level 1
contains non-primitive properties, and collections will be filled. These
child objects and objects in collections are set to the equivalent of
what we saw as level 0 for the first object. The pattern goes on, you
can copy to level 2, level 3, and so on. I’m going to make the argument
that you should not return a variable depth copy of level
higher than 2.
How does this satisfy the three issues I was looking to solve?
First, this
converts my domain object to a new object of the same class but without
Hibernate’s persistent collections. This prevents the exception.
Hibernate's persistent collections aren't copied, their contents are
copied into the natural collection of the target class.This way we don’t
need the HibernateUnproxifier, everything can be copied without knowledge of Hibernate.
Secondly,
the copy still needs to be performed inside a transaction, but it can
be done generically without needing to specify which specific
collections are loaded. The copier provides complete control over how
shallow or deep the copy/mapping occurs.
Finally,
this copy performs cycle detection and sets any repeated copies to
null. With this mapping technique, I was able to remove @JsonBackReference from all of my domain objects.
Caveats
It’s important to note that the copy
is done according to bean properties, not necessarily through field
reflection. Because of this, the object to be copied should be a domain
object following the Java Bean pattern. Additionally, the copier depends on Spring's BeanUtil, so it works best in a project already using Spring. If this were to be distributed as a more general purpose tool, I would probably try to rework it to use field reflection, and to not have other dependencies. As of this writing, this solution works fine in my project so this is the state it's in right now.
Conclusion
As
mentioned above, there are many ways to deal with
LazyInitializationExceptions. I thought this was a neat and useful idea.
If you have ever dealt with LazyInitializationExceptions, hopefully
you’ll find it useful too.
No comments:
Post a Comment