Spring 3.1, Hibernate 4 and Jackson-Module-Hibernate

| March 12, 2012 | 4 Replies

Those of you who use Spring MVC 3 with Hibernate have probably also used jackson-module-hibernate to handle json serialization from hibernate proxies. With the recent release of Hibernate 4 the old version of jackson-module-hibernate has become obsolete, and indeed, the people behind jackson-module-hibernate have created a new version. However, this new version is intended to work with Jackson 2. Sadly, Spring 3.1’s MappingJacksonHttpMessageConverter is hardcoded with Jackson 1.x.x in mind. So until there is a new Spring release that fixes this issue and you want to use Jackson 2 in your project, you can follow our solution:

1. Create a new HttpMessageConverter that uses Jackson 2.

In order to make  Spring’s MappingJacksonHttpMessageConverter work, all you have to do is change the package names for Jackson. The easiest way is to copy all the code from org.springframework.http.converter.json.MappingJacksonHttpConverter into a new class (for example MappingJackson2HttpMessageConverter). Replace the following imports:

import org.codehaus.jackson.JsonEncoding;
import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.JsonProcessingException;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.map.type.TypeFactory;
import org.codehaus.jackson.type.JavaType;

with

import com.fasterxml.jackson.core.JsonEncoding;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;
import com.fasterxml.jackson.databind.JavaType;

Another small tweak you’ll need to do is change the method:

protected JavaType getJavaType(Class<?> clazz) {
    return TypeFactory.type(clazz);
}

to

protected JavaType getJavaType(Class<?> clazz) {
    return TypeFactory.defaultInstance().constructType(clazz);
}

Here is the whole picture:

import java.io.IOException;
import java.nio.charset.Charset;
import java.util.List;

import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.AbstractHttpMessageConverter;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.http.converter.HttpMessageNotWritableException;
import org.springframework.util.Assert;

import com.fasterxml.jackson.core.JsonEncoding;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;

public class MappingJackson2HttpMessageConverter extends AbstractHttpMessageConverter<Object> {

    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

    private ObjectMapper objectMapper = new ObjectMapper();

    private boolean prefixJson = false;

    public MappingJackson2HttpMessageConverter() {
       super(new MediaType("application", "json", DEFAULT_CHARSET));
    }

    public void setObjectMapper(ObjectMapper objectMapper) {
        Assert.notNull(objectMapper, "ObjectMapper must not be null");
        this.objectMapper = objectMapper;
    }

    public ObjectMapper getObjectMapper() {
        return this.objectMapper;
    }

    public void setPrefixJson(boolean prefixJson) {
        this.prefixJson = prefixJson;
    }

    @Override
    public boolean canRead(Class<?> clazz, MediaType mediaType) {
        JavaType javaType = getJavaType(clazz);
        return (this.objectMapper.canDeserialize(javaType) && canRead(mediaType));
    }

    @Override
    public boolean canWrite(Class<?> clazz, MediaType mediaType) {
        return (this.objectMapper.canSerialize(clazz) && canWrite(mediaType));
    }

    @Override
    protected boolean supports(Class<?> clazz) {
        // should not be called, since we override canRead/Write instead
        throw new UnsupportedOperationException();
    }

    @Override
    protected Object readInternal(Class<?> clazz, HttpInputMessage inputMessage)
    throws IOException, HttpMessageNotReadableException {

        JavaType javaType = getJavaType(clazz);
        try {
            return this.objectMapper.readValue(inputMessage.getBody(), javaType);
        }
        catch (JsonProcessingException ex) {
            throw new HttpMessageNotReadableException("Could not read JSON: " + ex.getMessage(), ex);
        }
    }

    @Override
    protected void writeInternal(Object object, HttpOutputMessage outputMessage)
    throws IOException, HttpMessageNotWritableException {

        JsonEncoding encoding = getJsonEncoding(outputMessage.getHeaders().getContentType());
        JsonGenerator jsonGenerator =
        this.objectMapper.getJsonFactory().createJsonGenerator(outputMessage.getBody(), encoding);
        try {
            if (this.prefixJson) {
                jsonGenerator.writeRaw("{} && ");
            }
            this.objectMapper.writeValue(jsonGenerator, object);
        }
        catch (JsonProcessingException ex) {
            throw new HttpMessageNotWritableException("Could not write JSON: " + ex.getMessage(), ex);
        }
    }

    protected JavaType getJavaType(Class<?> clazz) {
        return TypeFactory.defaultInstance().constructType(clazz);
    }

    protected JsonEncoding getJsonEncoding(MediaType contentType) {
        if (contentType != null && contentType.getCharSet() != null) {
            Charset charset = contentType.getCharSet();
            for (JsonEncoding encoding : JsonEncoding.values()) {
                if (charset.name().equals(encoding.getJavaName())) {
                return encoding;
                }
            }
        }
    return JsonEncoding.UTF8;
    }
}

2. Create a HibernateAwareObjectMapper

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.hibernate4.Hibernate4Module;

public class HibernateAwareObjectMapper extends ObjectMapper {

    public HibernateAwareObjectMapper() {
        Hibernate4Module hm = new Hibernate4Module();
        registerModule(hm);
    }
}

3. Register the new MappingJackson2HttpMessageConverter with Spring.

Here’s a sample bean definition:

<mvc:message-converters>
	<bean class="com.pastelstudios.json.MappingJackson2HttpMessageConverter">
		<property name="objectMapper">
			<bean class="com.pastelstudios.json.HibernateAwareObjectMapper" />
		</property>
	</bean>
</mvc:message-converters>

If you are using <mvc:annotation-driven /> then it should wrap <mvc:message-converters> like this:

<mvc:annotation-driven>
<mvc:message-converters>
<!-- bean definitions go here -->
</mvc:message-converters>
</mvc:annotation-driven>

That’s it! You can now serialize hibernate proxies like this:

@RequestMapping

public @ResponseBody Person getPerson(@RequestParam(“personId”) int personId) {
    Person person = personDao.load(personId);
    return person;
}

without getting LazyInitialization exceptions.

Jackson 2, as well as its extensions, like jackson-module-hibernate, can be downloaded from https://github.com/FasterXML/.In the time of the writing of this article, the latest Jackson 2 version is Jackson 2.0 RC2, but this should work with the full release, too.

Attached code:
spring3-jackson2-src

Tags: , ,

Category: Development

About the Author ()

Comments (4)

Trackback URL | Comments RSS Feed

  1. ark says:

    Hi Atanasov,

    Have you used any specific json annpotations in the collections of the Person class? that force the correct serializer from Hibernate4Module to be used?

    • l.atanasov says:

      Hey, ark
      I haven’t tried the @JsonSerializer annotation (I assume this is what you had in mind) with this message converter. However, since Spring 3.1.2, Jackson 2 is officially supported, so if the annotation worked before, it should work again.

Leave a Reply


seven × = 35