SQL Zone is brought to you in partnership with:

I'm VP of Engineering at Tasktop Technologies, improving the effectiveness of developers worldwide via Tasktop's revolutionary task-focused interface, built on the Eclipse Mylyn open source framework. David has posted 28 posts at DZone. You can read more from them at their website. View Full User Profile

How I Lost My Annotations: JPA, Hibernate and FetchType.LAZY

09.04.2009
| 9688 views |
  • submit to reddit

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.

From http://greensopinion.blogspot.com

Published at DZone with permission of its author, David Green.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Tags:

Comments

Mladen Girazovski replied on Fri, 2009/09/04 - 5:50am

Since Hibernate is using Proxies, it will need a lot of workarounds for Problems that are caused by proxying, ie- with equals, hashcode and the "missing" annotations.

That was one of the reasons that i changed to EclipseLink, since it doesn't rely on Proxies, instead the byte code of your class will be changed.

Stevi Deter replied on Fri, 2009/09/04 - 11:21am

Interesting post. On my current project we're using Hibernate's validation annotations and haven't yet run into similar problems, but hopefully I'll remember to think about proxies if we do!

Andy Jefferson replied on Sat, 2009/09/05 - 3:02am

Or use an implementation that doesn't introduce proxies hence no problems. As per

The Trouble with Proxies

 

Why should you have to use workarounds to do persistence ?

--Andy (DataNucleus)

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.