The Journey for Handling Hibernate Lazy Loading using Spring 3 MVC Annotations and Jackson for JSON

I’m not even sure where to start with this one. Let’s see…

The Setup

Here’s the big picture.  A new project, new everything, lots of annotations… And I should add, I’m new to Hibernate and Spring…

  • Ext JS 4
  • Spring 3.0.4
  • Hibernate 3.6.2
  • Jackson 1.7.6

In my db, I have a User table.  Each user may have a Supervisor, which is just a user_id.  The User entity ends up looking like this:

@JsonAutoDetect
@Entity
@Table(name="PR_USER")
public class User {

	@Id
	@Column(name="USER_ID")
	private int userId;
	public int getUserId() { return userId; }
	public void setUserId(int userId) { this.userId = userId; }

	@ManyToOne(fetch=FetchType.LAZY)
	@JoinColumn(name="SUPERVISOR_ID")
	private User supervisor;
	public User getSupervisor() { return supervisor; }
	public void setSupervisor(User supervisor) { this.supervisor= supervisor; }
}

Helpful link: How to create relationship to the same entity with JPA (Hibernate)?

Issue 1 – Eagerly Load One Supervisor

Once I got it straight how to create the relationship to the same entity, everything was working well except for one thing… I only needed to eagerly load the user’s supervisor. But I didn’t need the supervisor’s supervisor and their supervisor and so on. After a lot of searching around (and I seem to have lost the link that helped me finally understand, but it might have just been the Hibernate documentation), I determined that in order to only load one “level” of supervisor I needed the “fetch=FetchType.LAZY” property on the @ManyToOne annotation for the supervisor column. Then my query needed to be built with HQL (previously I was using Criterias) so that I could specify to fetch the one supervisor eagerly. The HQL ended up looking something like this:

from User as user
left outer join fetch user.supervisor as supervisor
where user.fname like

I have a few different user queries, by first name, last name, supervisor, etc.

Great! It worked! Now my supervisor chain doesn’t go on and on. Now on to the next issue.

Issue 2 – Jackson mapping of Lazy Loaded objects

All ready to test and then bam! This error from the Jackson mapper:

org.codehaus.jackson.map.JsonMappingException: No serializer found for class org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationConfig.Feature.FAIL_ON_EMPTY_BEANS) ) (through reference chain: java.util.ArrayList[46]->....User_$$_javassist_2["handler"])
    at org.codehaus.jackson.map.ser.StdSerializerProvider$1.serialize(StdSerializerProvider.java:62)
    at org.codehaus.jackson.map.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:268)
    at org.codehaus.jackson.map.ser.BeanSerializer.serializeFields(BeanSerializer.java:146)
    at org.codehaus.jackson.map.ser.BeanSerializer.serialize(BeanSerializer.java:118)
    at org.codehaus.jackson.map.ser.ContainerSerializers$IndexedListSerializer.serializeContents(ContainerSerializers.java:236)
    at org.codehaus.jackson.map.ser.ContainerSerializers$IndexedListSerializer.serializeContents(ContainerSerializers.java:189)
    at org.codehaus.jackson.map.ser.ContainerSerializers$AsArraySerializer.serialize(ContainerSerializers.java:111)
    at org.codehaus.jackson.map.ser.StdSerializerProvider._serializeValue(StdSerializerProvider.java:296)
    at org.codehaus.jackson.map.ser.StdSerializerProvider.serializeValue(StdSerializerProvider.java:224)
    at org.codehaus.jackson.map.ObjectMapper.writeValue(ObjectMapper.java:925)
    at org.springframework.http.converter.json.MappingJacksonHttpMessageConverter.writeInternal(MappingJacksonHttpMessageConverter.java:153)

So, it turns out that Jackson does not know what to do with a Hibernate Lazy Loaded object. A lot of research and these helpful links led me to the following conclusion:

  1. There is a new, very new, completed untested Jackson-hibernate module out there to handle lazy loading, named jackson-module-hibernate.  https://github.com/FasterXML/jackson-module-hibernate.
  2. The example setup in jackson-module-hibernate has a configuration for AnnotationMethodHandlerAdapter.  Close reading of the comments in the “Serializing Hibernate entities into JSON using Jackson framwork” extremely helpful blog entry, says the AnnotationMethodHandlerAdpater configs are for REST only.  Currently, my setup is to use @ResponseBody to write the JSON the response, which uses AnnotationMethodHandlerAdapter.  So, it seems there are now two options.
    • No longer use @ResponseBody and instead create a “jsonView” and return that as the ModelAndView, as suggested in the comments of the post.
    • Make AnnotationMethodHandlerAdapter work.  This lead me to the incredibly helpful post: http://scottfrederick.blogspot.com/2011/03/customizing-spring-3-mvcannotation.html.  Which clearly explains the problem with AnnotationMethodHandlerAdapter and mvc annotations and a workaround for it, unless you can hang around for Spring 3.1.

I should also mention that I had to make changes to jackson-module-hibernate.  The key piece of code was commented out and the parameters were wrong.  So, after changing


public class HibernateSerializers implements Serializers
{
    ....
   public JsonSerializer<?> findSerializer(
        SerializationConfig config, JavaType type,
        BeanDescription beanDesc, BeanProperty beanProperty )
   {
       ....
       if (HibernateProxy.class.isAssignableFrom(raw)) {
        //  return new HibernateProxySerializer(type, config);
        //  return new PersistentCollectionSerializer(type, isEnabled(Feature.FORCE_LAZY_LOADING));
       }
      ....
   }
}

to

public class HibernateSerializers implements Serializers
{
    ....
    public JsonSerializer<?> findSerializer(
         SerializationConfig config, JavaType type,
         BeanDescription beanDesc, BeanProperty beanProperty )
    {
        ....
        if (HibernateProxy.class.isAssignableFrom(raw)) {
            return new HibernateProxySerializer( isEnabled(Feature.FORCE_LAZY_LOADING));
         // return new PersistentCollectionSerializer(type, isEnabled(Feature.FORCE_LAZY_LOADING));
        }
        ....
     }
}

With the above change to jackson-module-hibernate and also implementing Scott Frederick’s AnnotationMethodHandlerAdapterConfigurer, amazingly, it worked.  Hibernate only loaded the supervisor one level deep and Jackson handled the lazy loaded object and mapped the JSON as I expected and Ext JS showed the user data in my grid.

It was a journey…

Advertisements

5 comments

  1. happyprg :helloi was applied jackson-hibernate-moduleit was good.however i have a question.i have a follow exception.
    org.codehaus.jackson.map.JsonMappingException: N/A (through reference chain: jp.naver.cafe.post.MixPost[“videos”])at org.codehaus.jackson.map.deser.SettableBeanProperty._throwAsIOE(SettableBeanProperty.java:276)at org.codehaus.jackson.map.deser.SettableBeanProperty._throwAsIOE(SettableBeanProperty.java:259)at org.codehaus.jackson.map.deser.SettableBeanProperty$MethodProperty.set(SettableBeanProperty.java:344)at org.codehaus.jackson.map.deser.SettableBeanProperty$MethodProperty.deserializeAndSet(SettableBeanProperty.java:334)at org.codehaus.jackson.map.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:495)at org.codehaus.jackson.map.deser.BeanDeserializer.deserialize(BeanDeserializer.java:351)at org.codehaus.jackson.map.deser.CollectionDeserializer.deserialize(CollectionDeserializer.java:119)at org.codehaus.jackson.map.deser.CollectionDeserializer.deserialize(CollectionDeserializer.java:96)at org.codehaus.jackson.map.deser.CollectionDeserializer.deserialize(CollectionDeserializer.java:25)at org.codehaus.jackson.map.ObjectMapper._readMapAndClose(ObjectMapper.java:2129)at org.codehaus.jackson.map.ObjectMapper.readValue(ObjectMapper.java:1400)at jp.naver.cafe.test.AbstractAPIIntegrationTest.convertToList(AbstractAPIIntegrationTest.java:259)at jp.naver.cafe.post.PostsAPIIntegrationTest.findMixPostsTest(PostsAPIIntegrationTest.java:46)at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)at java.lang.reflect.Method.invoke(Unknown Source)at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:44)at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:15)at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:41)at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:20)at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:74)at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:82)at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:72)at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:240)at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:49)at org.junit.runners.ParentRunner$3.run(ParentRunner.java:193)at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:52)at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:191)at org.junit.runners.ParentRunner.access$000(ParentRunner.java:42)at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:184)at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)at org.junit.runners.ParentRunner.run(ParentRunner.java:236)at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:180)at org.eclipse.jdt.internal.junit4.runner.JUnit4TestReference.run(JUnit4TestReference.java:49)at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:467)at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:683)at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:390)at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:197)Caused by: java.lang.NullPointerExceptionat jp.naver.cafe.post.MixPost.setVideos(MixPost.java:78)at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)at java.lang.reflect.Method.invoke(Unknown Source)at org.codehaus.jackson.map.deser.SettableBeanProperty$MethodProperty.set(SettableBeanProperty.java:342)… 38 more
    how can i fix it?somebody help me plz

    1. I hope you have figured out what the issue is. I’m not a Jackson expert by any means. But, you can download the source and step through it to debug.

  2. For what it’s worth, root cause is mentioned there in the stack trace: “by: java.lang.NullPointerException at …”.

    So it looks like one of setter methods (setVideos(), maybe, looking at property chain) is trying to deference a null pointer. Exception is not from Jackson code, but is caught by Jackson when trying to set a bean property value.

  3. Could you please let me know your point “So it looks like one of setter methods (setVideos(), maybe, looking at property chain) is trying to deference a null pointer”?
    How we can add property chain?
    Your earliest guidance is much appreciated.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s