Lazy Loadng error in JSON serializer

2020-02-14 04:47发布

问题:

I have such kind of @OneToOne Hibernate relationShip

public class Address implements Serializable {

    private String id;
    private String city;
    private String country;
//setter getters ommitted
}

public class Student implements Serializable {

    private String id;
    private String firstName;
    private String lastName;    
    private Address address;
}

address Item is mapped as LAZY.

Now I want to fetch user and it's address using

session.load(Student.class,id);

In my daoService.

Then I return it as JSON from my Spring MVC controller:

@RequestMapping(value="/getStudent.do",method=RequestMethod.POST)
    @ResponseBody
    public Student getStudent(@RequestParam("studentId") String id){
        Student student = daoService.getStudent(id);
        return student;
    }

Unfortunately, it's not working because of Lazy clasees and I fails with:

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: com.vanilla.objects.Student_$$_javassist_1["address"]->com.vanilla.objects.Address_$$_javassist_0["handler"])
    at org.codehaus.jackson.map.ser.StdSerializerProvider$1.serialize(StdSerializerProvider.java:62)

I do use OpenSessionInViewInterceptor and it works just fine. I understand that I can user left join HQL query and retrieve student and address that way and solve the problem. I also understand that changing relation to EAGER will solve it.

But how can I serialize to JSON lazy classes using standard jackson message converter which of cause I added to my XML file.

回答1:

The easiest solution: Don't serialize entities, use Value Objects.

If that is not an option for you, make sure that the entity Object is detached.

With JPA (2), you would use EntityManager.detach(entity), with plain Hibernate the equivalent is Session.evict(entity).



回答2:

Once I write a processor to handle this but now it's easy to fix this by using the jackson hibernate module.



回答3:

Within your DAO method add Hibernate.initialize(<your getter method>); to resolve this.

Student student = findById(<yourId>);
Hibernate.initialize(student.getAddress());
...
return student;

Try like the above.



回答4:

There is another option that solves your problems. You can add this filter in web.xml

<filter>
    <filter-name>springOpenEntityManagerInViewFilter</filter-name>
    <filter-class>org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter</filter-class>
    <init-param>
      <param-name>entityManagerFactoryBeanName</param-name>
      <param-value>entityManagerFactory</param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>springOpenEntityManagerInViewFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

The problem is that entities are loaded lazy and serialization happens before they get loaded fully.



回答5:

But how can I serialize to JSON lazy classes using standard jackson message converter which of cause I added to my XML file.

First of all, I don't advise to use DTO/Value Object only to solve this issue.
You may find it easy at the beginning but at each new development/change, the duplicate code means making twice modifications at each time... otherwise bugs.

I don't mean that VO or DTO are bad smells but you should use them for reasons they are designed (such as providing a content/structure that differs according to logical layers or solving an unsolvable serialization problem).
If you have a clean and efficient way to solve the serialization issue without VO/DTO and you don't need them, don't use them.

And about it, there is many ways to solve lazy loading issue as you use Jackson with Hibernate entities.

Actually, the simplest way is using FasterXML/jackson-datatype-hibernate

Project to build Jackson module (jar) to support JSON serialization and deserialization of Hibernate (http://hibernate.org) specific datatypes and properties; especially lazy-loading aspects.

It provides Hibernate3Module/Hibernate4Module/Hibernate5Module, extension modules that can be registered with ObjectMapper to provide a well-defined set of extensions related to Hibernate specificities.

To do it working, you just need to add the required dependency and to add the Jackson Module available during processings where it is required.

If you use Hibernate 3 :

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

If you use Hibernate 4 :

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

And so for...

Where jackson.version.datatype should be the same for the used Jackson version and the ackson-datatype extension.

If you use or may use Spring Boot, you just need to declare the module as a bean in a specific Configuration class or in the SpringBootApplication class and it will be automatically registered for any Jackson ObjectMapper created.

The 74.3 Customize the Jackson ObjectMapper Spring Boot section states that :

Any beans of type com.fasterxml.jackson.databind.Module will be automatically registered with the auto-configured Jackson2ObjectMapperBuilder and applied to any ObjectMapper instances that it creates. This provides a global mechanism for contributing custom modules when you add new features to your application.

For example :

@Configuration
public class MyJacksonConfig {

    @Bean
    public Module hibernate5Module() {
      return new Hibernate5Module();
    }
}

or :

@SpringBootApplication
public class AppConfig {

    public static void main(String[] args) throws IOException {
      SpringApplication.run(AppConfig.class, args);
    }

    @Bean
    public Module hibernate5Module() {
      return new Hibernate5Module();
    }
}