Achaius
Achaius

Reputation: 6124

How to serialize a json containing LAZY associations

I am having an Person entity which has @ManyToOne association with Contact entity with fetch type LAZY. I am using spring-boot to expose REST API. One of my POST call contains nested JSON to save the parent entity Person along with association Contact

Since Contact fetch type is LAZY, I am encountering into following exception

    ERROR 17415 --- [nio-8080-exec-4] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.http.converter.HttpMessageConversionException: Type definition error: [simple type, class org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: com.example.rest.RestResultObject["results"]->java.util.ArrayList[0]->com.example.model.Person["contact"]->com.example.model.Contact_$$_jvst8d1_4["handler"])] with root cause

com.fasterxml.jackson.databind.exc.InvalidDefinitionException: No serializer found for class org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) (through reference chain: com.example.rest.RestResultObject["results"]->java.util.ArrayList[0]->com.example.model.Person["contact"]->com.example.model.Contact_$$_jvst8d1_4["handler"])
at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:77) ~[jackson-databind-2.9.3.jar:2.9.3]

Without changing Contact to EAGER. Is there any best way to solve this problem?

Updated:

Person.java

public class Person {
    private long id;
    private String name;
    private String rno;
    @ManyToOne(fetch = FetchType.LAZY)
    private Contact contact;

    // Getters and setters
}

Contact.java

public class Contact {
    private long id;
    private String info;
    @OneToMany
    private List<Person> persons;
}

Upvotes: 4

Views: 13954

Answers (3)

Jimmy
Jimmy

Reputation: 1051

This is the one way to solve the problem by using @JsonIgnore annotation along with fethType.LAZY

public class Person {
    private long id;
    private String name;
    private String rno;
    @ManyToOne(fetch = FetchType.LAZY)
    @JsonIgnore
    private Contact contact;

    // Getters and setters
}

public class Contact {
    private long id;
    private String info;
    @OneToMany
    private List<Person> persons;
}

Upvotes: 0

maruf571
maruf571

Reputation: 1945

I have added the following things

  • @Entity on each object
  • Change long to Long in id, this will help you if you use spring data JPA
  • add @Id to declare id as primary key
  • @JsonBackReference & @JsonManagedReference to avoid infinite loop by Jackson

Person class

@Entity
public class Person {

 @Id
 @GeneratedValue(strategy = GenerationType.IDENTITY)
 private Long id;

 private String name;

 private String rno;

 @JsonManagedReference
 @ManyToOne(fetch = FetchType.LAZY)
 private Contact contact;

 //setter & getter

}

Contact class

@Entity
public class Contact {

 @Id
 @GeneratedValue(strategy = GenerationType.IDENTITY)
 private Long id;

 private String info;

 @JsonBackReference
 @OneToMany(cascade = CascadeType.ALL, mappedBy = "contact")
 private List<Person> persons;

 //setter & getter
}

and add a dependency

<dependency>
        <groupId>com.fasterxml.jackson.datatype</groupId>
        <artifactId>jackson-datatype-hibernate5</artifactId>
</dependency>

and finally add a new config

@Configuration
public class JacksonConfig {

@Bean
public Jackson2ObjectMapperBuilderCustomizer addCustomBigDecimalDeserialization() {
    return new Jackson2ObjectMapperBuilderCustomizer() {
        @Override
        public void customize(Jackson2ObjectMapperBuilder jacksonObjectMapperBuilder) {
            jacksonObjectMapperBuilder.featuresToDisable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
            jacksonObjectMapperBuilder.modules(new Hibernate5Module());
        }

    };
}
}

Upvotes: 5

piradian
piradian

Reputation: 444

The problem, most likely, is related to transaction processing. JSON serialization is executed out of transaction scope. If so, the easiest solution (not necessarily the best from the architectural point of view) is to create the service that wrap the entity loading (dedicated for REST operations) and perform "delazyfication" of related data, e.g. (the key element is @Transactional anotation).

@Sevice
@Transactional(readOnly=true)
public class DataReaderServiceImpl extends DataLoaderService{
    //initialization code
    public Person loadPerson(PredicatesType somePredicate){
        Person person = //get person using predicates expression
        //"delazy" contacts in transaction scope
        person.getContacts();
        return person;
    }
}

Architecturally, it would be better, in such a service, to have mapping to DTOs and return DTO instances instead of entities.

Upvotes: 1

Related Questions