Ali Akbarpour
Ali Akbarpour

Reputation: 964

JSON serializing and deserializing with hibernate jpa to have parent object into child in JSON response

I am developing rest web app with spring framework, Hibernate and JSON. Please Assume that I have two entities like below:

BaseEntity.java

@MappedSuperclass
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,property = "id" )
public abstract class BaseEntity implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    public long getId() {
        return id;
    }
}

University.java

 public class University extends BaseEntity {

      private String uniName;

       @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER,orphanRemoval = true)
      @JoinColumn(name = "university_id")
        private List<Student> students=new ArrayList<>();
    // setter an getter
    }

Student.java

    public class Student extends BaseEntity{

        private String stuName;

        @ManyToOne(fetch = FetchType.EAGER)
        @JoinColumn(name = "university_id",updatable = false,insertable = false)   
        private University university;

    // setter an getter
        }

when I call my rest api to list University every things work fine as I expect, but when I call my rest api to list student eagerly my JSON response is

[
   {
    "id": 1,
    "stuName": "st1",
    "university": {
        "id": 1,
        "uniName": "uni1"
                 }
    },
    {
        "id": 2,
        "stuName": "st2",
        "university": 1
    }
]

but my desired response is:

[
    {
        "id": 1,
        "stutName": "st1",
        "university": 
        {
         "id": 1,
        "uniName": "uni1"
        }
    },
    {
        "id": 2,
        "stutName": "st2",
        "university": 
        {
         "id": 1,
        "uniName": "uni1"
        }
    }

Update 1: my hibernate annotation working fine I have JSON issue

Requirements :

  1. I need both side fetch eagerly(the university side is Ok)

  2. I need university object in student side for every student(when I fetching student eagerly)

What kind of serialization or JSON config I need to do that for matching my desired response?

Update 2:

by removing @JsonIdentityInfo and editing student side like below:

@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "university_id",updatable = false,insertable = false)
@JsonIgnoreProperties(value = "students", allowSetters = true)
private University university;

the json response still same I need my desired response that is mentioned above.

thanks

Upvotes: 10

Views: 10997

Answers (8)

Daniele Licitra
Daniele Licitra

Reputation: 1608

I had the same problem. Hibernate (or eclipselink) are not the problem. The only constraint in JPA is the FetchType.EAGER .

In the BaseEntity I have added a standard method

public String getLabel(){
   return "id:"+this.getId();
}

this method would be abstract, but I had a lot of class and i didn't want to change it all so I added a default value.

In parent entity, in this case University, override the method

@Override
public String getLabel{
    return this.uniName;
}

For each parent class, use a particular field as a label for your entity

Define a MyStandardSerializer:

public class StandardJsonSerializer extends JsonSerializer<EntityInterface> {

@Override
public void serializeWithType(EntityInterface value, JsonGenerator jgen, SerializerProvider provider, TypeSerializer typeSer) throws IOException, JsonProcessingException {
        serialize(value, jgen, provider); 
}

@Override
public void serialize(EntityInterface value, JsonGenerator jgen, SerializerProvider provider) 
  throws IOException, JsonProcessingException {
    jgen.writeStartObject();
    jgen.writeNumberField("id", (long) value.getId());
    jgen.writeStringField("label", value.getLabel());
    jgen.writeEndObject();
}

}

In the student class, on univrsity add:

@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "university_id",updatable = false,insertable = false)   
@JsonSerialize(using=StandardJsonSerializer.class)
private University university;

Now you have resolved circularity. When you need a label, override the method in the parent entity. When you need some particular fields, create a specific Serializer.

Upvotes: 1

R&#233;mi Bantos
R&#233;mi Bantos

Reputation: 1967

The way you map your relationsip, even if it is "working fine", does not comply with jpa spec for bi-directional relationship. See this question and related answers.

To summarize usually, the owner of the relationship is the many-to-one side and the one-to-many side is annotated with mappedBy. Your mapping solution, with the one-to-many side owning the relationship is not usual / recommended (as described in the answers above) but technically possible. (@manyToOne side misses some attributes like "updatable=false" in your example)

Then, with JPA and recent Hibernate version, the lazy loading policy is the following:

 OneToMany: LAZY
 ManyToOne: EAGER
 ManyToMany: LAZY
 OneToOne: EAGER

So I would suggest you to use this default lazy loading policy, and to change the owner of your manyToOne relationship as it does not seem like a good idea to get all the students via a single University resource request. (Have you heard about pagination?)

Doing so, and also excluding students collection from Marshalling, using for example @JsonIgnore, should do the trick.

Upvotes: 1

Piotr Wilkin
Piotr Wilkin

Reputation: 3491

You might want to try using @JsonRawValue as an annotation for your university property. The behavior you're encountering is due to reference collapsing - since it's the same University twice, the serializer tries to be smart and just return a reference the second time it's encountered.

EDIT: The toString():

@Override
public String toString() {
    ObjectMapper mapper = new ObjectMapper();
    return mapper.writeValueAsString(this);
}

Upvotes: 1

vishal paalakurthi
vishal paalakurthi

Reputation: 138

Add @jsonignore for getter method
and add @jsonProperty to the field like

@JsonProperty(access = Access.READ_ONLY)
private String password;

Recently added some feature to jackson like Readonly and writeonly
you can refer this:
http://fasterxml.github.io/jackson-annotations/javadoc/2.6/com/fasterxml/jackson/annotation/JsonProperty.Access.html

Upvotes: 1

Chris
Chris

Reputation: 999

I understand you do not want to include University.students in your JSON.

  1. Remove @JsonIdentityInfo

    @MappedSuperclass
    //@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class,property = "id" )
    public abstract class BaseEntity implements Serializable {
    
        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private long id;
        public long getId() {
            return id;
        }
    }
    
  2. Add @JsonIgnore to students to avoid circle

    public class University extends BaseEntity {
    
        private String uniName;
    
        @JsonIgnore
        @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER,orphanRemoval = true)
        @JoinColumn(name = "university_id",foreignKey = @ForeignKey(name = "university_id"))
        private List<Student> students=new ArrayList<>();
        // setter an getter
    }
    

If you need University.students to be serialized in other contexts give http://www.baeldung.com/jackson-bidirectional-relationships-and-infinite-recursion a read. Other options to deal with bidirectional relationships are explained there.

Upvotes: 1

Hema
Hema

Reputation: 986

You can add this and check

University

public class University {

@Fetch(value = FetchMode.SELECT)
@OneToMany(cascade = CascadeType.ALL)
@JoinColumn(name = "university_id")
@JsonIgnore 
 private List<Student> students;

}

Student

public class Student{
@ManyToOne
@JoinColumn(name = "university_id", insertable = true, updatable = true, nullable = true)
private University university;
}

Upvotes: 1

Tharsan Sivakumar
Tharsan Sivakumar

Reputation: 6531

Can you add @JoinColumn to Student entity as well

@ManyToOne(fetch = FetchType.EAGER)  
@JoinColumn(name = student_id")

Also check your University entity class's foreign key.The foreign key should be from other entity right? @JoinColumn(name = "university_id",foreignKey = @ForeignKey(name = "student_id")) ??

Else alternatively you can use the "mappedBy" as well.

@JoinColumn(name = "university_id", mappedBy="university")
        private List<Student> students=new ArrayList<>();

Upvotes: 1

Sachin Gupta
Sachin Gupta

Reputation: 8368

Remove @JsonIdentityInfo from base class, this is causing university object to serialize only id.

Upvotes: 1

Related Questions