How I Lost My Annotations: JPA, Hibernate and FetchType.LAZY
Many JPA applications use Java annotations on their domain model classes in order to specify validation rules. Runtime frameworks use reflection to discover these validation rules and enforce them at runtime. This all works nicely until you realize that you need FetchType.LAZY
on some of your @ManyToOne associations. After specifying lazy fetching you may find that some of your annotations disappear – only not all the time. So what's going on here? This article discusses the implications of FetchType.LAZY
and how to overcome this intermittent problem.
In order to implement lazy fetching under the covers Hibernate enhances your classes. What does this mean? Basically, Hibernate extends your classes and overrides its methods at runtime. It does this by using one of two bytecode manipulation libraries: Javassist or CGLib. If you ask one of these enhanced classes for its name, you'll end up with something like this:
// JPA entity class name
greensopinion.Person
// CGLib enhanced class name
greensopinion.Person$$EnhancerByCGLIB$$4ad0592d
// Javassist enhanced class name
greensopinion.Person_$$_javassist_0
Hibernate only does this when necessary. So when you get an instance of your entity it might be enhanced, and it might not – depending on how it was loaded. This can be confusing, since instances of a Person may not always be the same class. For example, code like this can be problematic:
Person person1 = entityManager.find(Person.class,id1);
Person person2 = entityManager.find(Person.class,id2);
// bad: contrary to what we'd expect, this comparison may
// be true sometimes, but not always
if (person1.getClass() == person2.getClass()) {
}
// bad: this may not work as expected either
if (person1.getClass().isAssignableFrom(person2.getClass())) {
}
// bad: again, sometimes true sometimes false
if (person1.getClass().isAssignableFrom(Person.class)) {
}
// this is ok: comparison will always be true (assuming person1 is not null)
if (person1 instanceof Person) {
}
In the above example comparisons that look straight-forward may behave differently depending on how and if these entities were already loaded by the same entity manager. This can be tricky when implementing hashCode and equals.
Furthermore, we're in for more trouble if we declare Person as follows:
@Entity
public class Person {
...
@Required
public String getFirstName() { ... }
}
Here we're using an annotation to indicate to our validation framework that the firstName property is a required field (see JSR-303, Hibernate Validation, Spring Modules, OVal). Our validation framework may do something like this:
Class clazz = entity.getClass();
// iterate on class accessors
...
if (method.getAnnotation(Required.class) != null) {
...
}
This may work well most of the time, and may work in all of our unit tests – however in the running application it may appear as if our class has lost its annotations! Why is this happening? Don't worry, your JVM isn't losing its head, or your annotations. This code may not work as expected if our entity is an enhanced version since Hibernate's ProxyFactory enhancers don't consider annotations when overriding methods on your entity.
So why use FetchType.LAZY
at all? Most non-trivial JPA applications will have to make use of FetchType.LAZY
in order to avoid @ManyToOne associations from causing cascading loads. Using FetchType.LAZY
can also reduce the number of joins when fetching collections, which in some cases greatly improves performance.
So how to we solve this problem? Our reflection code must be aware of HibernateProxy. Here's an example of how this can be fixed:
Class clazz = PersistenceUtil.getEntityClass(entity);
... now do reflection ...
// in PersistenceUtil.java
public static Class getEntityClass(Object entity) {
if (entity instanceof HibernateProxy) {
return ((HibernateProxy)entity).getHibernateLazyInitializer().getPersistentClass();
}
return entity.getClass();
}
The intermittent and in some cases occasional nature of the symptoms involved make this problem hard to reproduce. Lazy fetching may seem like a panacea, however it's another case where leaky abstractions make our lives more challenging. As with most things in software development, it's important to understand the implementation details in order to work effectively with JPA.
Recent Posts
- Flutter Maps With Vector Tiles
- Raspberry Pi SSH Setup
- Raspberry Pi Development Flow
- Troubleshooting Android App Crash on Chromebook
- Article Index
subscribe via RSS