Avoid Jackson serialization on non fetched lazy objects

86,014

Solution 1

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!

Solution 2

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

Solution 3

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 application and you want to keep the ability to modify Jackson features from application.properties file.

Solution 4

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

Solution 5

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.

Share:
86,014
r1ckr
Author by

r1ckr

SOreadytohelp

Updated on May 28, 2021

Comments

  • r1ckr
    r1ckr almost 3 years

    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.

  • Robin
    Robin over 9 years
    Thanks for sharing the solution. However after a few tries, I found that the latest jackson at the moment (2.4.2) does NOT work with spring 4.0.6. I still have the "no session" error. Only when I down graded jackson version back to 2.3.4 (or 2.4.2), did it start working.
  • LanceP
    LanceP almost 9 years
    Thanks for pointing MappingJackson2HttpMessageConverter out! I was looking for a class like this for a while.
  • Isthar
    Isthar about 8 years
    Thank you very much! I have just tested it and its working with Spring 4.2.4, Jackson 2.7.1-1 and JacksonDatatype 2.7.1 :)
  • hemu
    hemu almost 8 years
    How can we achieve this (Configuring Jackson to avoid lazy loaded object) using application.properties file?
  • Alessandro C
    Alessandro C almost 8 years
    This doesn't resolve the problem in Spring Data REST.
  • Zeemee
    Zeemee over 7 years
    For Hibernate 5 (as in the current version of Spring Boot), it is Hibernate5Module. It additionally needs a dependency to <groupId>com.fasterxml.jackson.datatype</groupId><artifactId‌​>jackson-datatype-hi‌​bernate5</artifactId‌​>
  • Ryan D
    Ryan D about 7 years
    This works perfectly for me except that I call hibernate5Module.enable(Feature.FORCE_LAZY_LOADING); That was required to not return "null"
  • Krish
    Krish over 6 years
    @rgdayo It is working for json. then what about xml ?
  • SalutonMondo
    SalutonMondo over 6 years
    don't forget to add Maven dependency. <!-- mvnrepository.com/artifact/com.fasterxml.jackson.datatype/… --> <dependency> <groupId>com.fasterxml.jackson.datatype</groupId> <artifactId>jackson-datatype-hibernate5</artifactId> <version>2.7.9</version> </dependency>
  • kapad
    kapad about 6 years
    That's not what is being said here. @Luis Belloch's solution suggests that you create the Jackson2ObjectMapperBuilder so that you don't override spring boots default message converter and continue to use the application.properties values to control jackson properties.
  • Eric Huang
    Eric Huang almost 6 years
    How do you configure this with spring boot when you are not using Spring Data Rest?
  • Eric Huang
    Eric Huang almost 6 years
    sorry... I am new where would you implement this method? With spring boot It uses Spring 5 which WebMvcConfigurerAdapter is deprecated
  • ranjith Gampa
    ranjith Gampa over 5 years
    How to solve the same issue, if Springboot is used in place of Spring?. I have tried registering the Hibernate4 module(which is the version I have currently), but that did not solve my issue.
  • Daniel Henao
    Daniel Henao over 5 years
    Do you have an update about this topic? I'm having the issue currently. Ofcourse whenI add the hibernate5Module (because im using hibernate 5), jackson ignores the proxy of non initialized objects, but it seems that the " well-known modules" are not being registered despite I useyour approach. I do know this, because some parts of my application are broken, and when I remove the Module, they work again.
  • Markus Pscheidt
    Markus Pscheidt over 5 years
    @DanielHenao Have you studied spring.io/blog/2014/12/02/…? I switched to Hibernate5, using the DelegatingWebMvcConfiguration class (instead of WebMvcConfigurerAdapter) as suggested by the Jackson-Antpath-Filter: @Override public void configureMessageConverters(List<HttpMessageConverter<?>> messageConverters) { messageConverters.add(jacksonMessageConverter()); addDefaultHttpMessageConverters(messageConverters); }
  • Kevin Orriss
    Kevin Orriss over 5 years
    Been trying to fix this for two days and this answer finally fixed it! For those stuck, at the time of writing this, the only other thing I did was ensure I was using the latest spring / hibernate etc
  • Van Dame
    Van Dame over 5 years
    Worked for me as well on Spring Boot 2.1. Was beginning to drive me crazy.
  • aardbol
    aardbol over 5 years
    It's not clear how to implement this. I get an incompatible types error.
  • GarouDan
    GarouDan over 5 years
    Awesome! Worked for me with Spring Boot 2.0.6 and Hibernate 5.
  • peterong
    peterong over 5 years
    Hi. Thanks for your answer. FYI, your XML configuration need to be put inside <mvc:annotation-driven></mvc:annotation-driven> in order to work. This also assume you have properly declared the schema location.
  • gburgalum01
    gburgalum01 almost 5 years
    This was very helpful. It should be pointed out that registering the Hibernate5Module class instance as a bean isn't enough. If you wish to apply it to a single ObjectMapper instance, you can autowire the module into your Spring-managed class and then register it with the ObjectMapper instance. @Autowire Module jacksonHibernateModule; ObjectMapper mapper = new ObjectMapper(); mapper.registerModule(this.jacksonHibernateModule).
  • Dmitry Kaltovich
    Dmitry Kaltovich about 4 years
    This wouldn't help
  • Dmitry Kaltovich
    Dmitry Kaltovich about 4 years
    1. Hibernate4Module is legacy . 2 SerializationFeature.WRITE_DATES_AS_TIMESTAMPS - not about the question
  • Dmitry Kaltovich
    Dmitry Kaltovich about 4 years
    Not about the question
  • Dmitry Kaltovich
    Dmitry Kaltovich about 4 years
    The solution could be much more easier: kotlin @Bean fun hibernate5Module(): Module = Hibernate5Module()
  • Dmitry Kaltovich
    Dmitry Kaltovich about 4 years
    xml config is legacy
  • Dmitry Kaltovich
    Dmitry Kaltovich about 4 years
    Not enough. You also need @Bean hibernate5Module()
  • Dmitry Kaltovich
    Dmitry Kaltovich about 4 years
    Hibernate4Module is legacy
  • Markus Pscheidt
    Markus Pscheidt about 4 years
    @DmitryKaltovich ad 1) it wasn't legacy at the time of writing; ad 2) ok, removed.
  • Dmitry Kaltovich
    Dmitry Kaltovich about 4 years
    Not good solution, because: 1. Hibernate4Module is legacy. 2 Too much lines of code. 3. You are changing existing preconfigured Message Converter
  • bluelurker
    bluelurker almost 3 years
    @EarthMind It may be too late, but for someone reading this now, Module is of type com.fasterxml.jackson.databind.Module and not java.lang.Module.
  • Johannes
    Johannes over 2 years
    thanks @chrismarx, I'd give you 1337 upvotes for this answer if SO let me.