Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Avoid Jackson serialization on non fetched lazy objects

I have a simple controller that return a User object, this user have a attribute coordinates that have the hibernate property FetchType.LAZY.

When I try to get this user, I always have to load all the coordinates to get the user object, otherwise when Jackson try to serialize the User throws the exception:

com.fasterxml.jackson.databind.JsonMappingException: could not initialize proxy - no Session

This is due to Jackson is trying to fetch this unfetched object. Here are the objects:

public class User{

    @OneToMany(fetch = FetchType.LAZY, mappedBy = "user")
    @JsonManagedReference("user-coordinate")
    private List<Coordinate> coordinates;
}

public class Coordinate {

    @ManyToOne
    @JoinColumn(name = "user_id", nullable = false)
    @JsonBackReference("user-coordinate")
    private User user;
}

And the controller:

@RequestMapping(value = "/user/{username}", method=RequestMethod.GET)
public @ResponseBody User getUser(@PathVariable String username) {

    User user = userService.getUser(username);

    return user;

}

There is a way to tell Jackson to not serialize the unfetched objects? I've been looking other answers posted 3 years ago implementing jackson-hibernate-module. But probably it could be achieved with a new jackson feature.

My versions are:

  • Spring 3.2.5
  • Hibernate 4.1.7
  • Jackson 2.2

Thanks in advance.

like image 342
r1ckr Avatar asked Feb 11 '14 17:02

r1ckr


5 Answers

I finally found the solution! thanks to indybee for giving me a clue.

The tutorial Spring 3.1, Hibernate 4 and Jackson-Module-Hibernate have a good solution for Spring 3.1 and earlier versions. But since version 3.1.2 Spring have his own MappingJackson2HttpMessageConverter with almost the same functionality as the one in the tutorial, so we don't need to create this custom HTTPMessageConverter.

With javaconfig we don't need to create a HibernateAwareObjectMapper too, we just need to add the Hibernate4Module to the default MappingJackson2HttpMessageConverter that Spring already have and add it to the HttpMessageConverters of the application, so we need to:

  1. Extend our spring config class from WebMvcConfigurerAdapter and override the method configureMessageConverters.

  2. On that method add the MappingJackson2HttpMessageConverter with the Hibernate4Module registered in a previus method.

Our config class should look like this:

@Configuration
@EnableWebMvc
public class MyConfigClass extends WebMvcConfigurerAdapter{

    //More configuration....

    /* Here we register the Hibernate4Module into an ObjectMapper, then set this custom-configured ObjectMapper
     * to the MessageConverter and return it to be added to the HttpMessageConverters of our application*/
    public MappingJackson2HttpMessageConverter jacksonMessageConverter(){
        MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();

        ObjectMapper mapper = new ObjectMapper();
        //Registering Hibernate4Module to support lazy objects
        mapper.registerModule(new Hibernate4Module());

        messageConverter.setObjectMapper(mapper);
        return messageConverter;

    }

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        //Here we add our custom-configured HttpMessageConverter
        converters.add(jacksonMessageConverter());
        super.configureMessageConverters(converters);
    }

    //More configuration....
}

If you have an xml configuration, you don't need to create your own MappingJackson2HttpMessageConverter either, but you do need to create the personalized mapper that appears in the tutorial (HibernateAwareObjectMapper), so your xml config should look like this:

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

Hope this answer be understandable and helps someone find the solution for this problem, any questions feel free to ask!

like image 111
r1ckr Avatar answered Nov 05 '22 05:11

r1ckr


As of Spring 4.2 and using Spring Boot and javaconfig, registering the Hibernate4Module is now as simple as adding this to your configuration:

@Bean
public Module datatypeHibernateModule() {
  return new Hibernate4Module();
}

ref: https://spring.io/blog/2014/12/02/latest-jackson-integration-improvements-in-spring

like image 38
chrismarx Avatar answered Nov 05 '22 06:11

chrismarx


In the case of Spring Data Rest then, while the solution posted by @r1ckr works, all that is required is to add one of the following dependencies depending on your Hibernate version:

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-hibernate4</artifactId>
    <version>${jackson.version}</version>
</dependency>

or

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-hibernate5</artifactId>
    <version>${jackson.version}</version>
</dependency>

Within Spring Data Rest there is a class:

org.springframework.data.rest.webmvc.json.Jackson2DatatypeHelper

which will auto-detect and register the Module on application start-up.

There is however an issue:

Issue Serializing Lazy @ManyToOne

like image 28
Alan Hay Avatar answered Nov 05 '22 05:11

Alan Hay


This is similar to accepted solution by @rick.

If you don't want to touch existing message converters configuration you can just declare a Jackson2ObjectMapperBuilder bean like:

@Bean
public Jackson2ObjectMapperBuilder configureObjectMapper() {
    return new Jackson2ObjectMapperBuilder()
        .modulesToInstall(Hibernate4Module.class);
}

Do not forget to add the following dependency to your Gradle file (or Maven):

compile 'com.fasterxml.jackson.datatype:jackson-datatype-hibernate4:2.4.4'

Useful if you have a spring-boot application and you want to keep the ability to modify Jackson features from application.properties file.

like image 31
Luis Belloch Avatar answered Nov 05 '22 07:11

Luis Belloch


I've spent whole day trying to solve the same problem. You can do it without changing existing message converters configuration.

In my opinion the easiest way to solve this problem only with 2 steps with help of jackson-datatype-hibernate:

kotlin example (same as java):

  1. Add In build.gradle.kts:
implementation("com.fasterxml.jackson.datatype:jackson-datatype-hibernate5:$jacksonHibernate")
  1. Create @Bean
   @Bean
   fun hibernate5Module(): Module = Hibernate5Module()

  • Notice that Module is com.fasterxml.jackson.databind.Module, not java.util.Module

  • Also good practice is to add @JsonBackReference & @JsonManagedReference to @OneToMany & @ManyToOne relationships. @JsonBackReference could be only 1 in class.

like image 6
Dmitry Kaltovich Avatar answered Nov 05 '22 05:11

Dmitry Kaltovich