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:
- 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.
- 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…