10

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

Ali Akbarpour
  • 958
  • 2
  • 18
  • 35
  • Add `@JsonIgnore` and try – Hema Oct 11 '17 at 12:56
  • @Hema I need to have university object in student side instead of university id – Ali Akbarpour Oct 11 '17 at 12:59
  • yes for the same i have provided code.. You will get university in Student – Hema Oct 11 '17 at 13:04
  • @Generic Don't think it can be done in a simple generic way. And you think that `list University is Ok` because you don't have duplicates there. `@JsonIdentityInfo` ID/reference mechanism works so that an object instance is only completely serialized once and referenced by its ID elsewhere https://github.com/FasterXML/jackson-databind/issues/372 – varren Oct 15 '17 at 13:11
  • Can you please check https://stackoverflow.com/a/22617567/3530898 – Amit K Bist Oct 16 '17 at 17:11
  • @ Amit K Bist that response dosnt work with create operation if you try to add parent with childerens the parent foreign key will not persist in db. – Ali Akbarpour Oct 17 '17 at 09:39
  • the exception is:null value in column "parent_id" violates not-null constraint – Ali Akbarpour Oct 17 '17 at 09:43
  • So you are telling it works for student 1 but somehow it does not work for student 2???? – Herr Derb Oct 20 '17 at 12:40
  • json refuse to sterilize repeated objects in response and assign just university id instead of complete object – Ali Akbarpour Oct 20 '17 at 12:44
  • @Herr Derb and if I can not solve this issue I have to implement recursive or loop for finding this briefed university object data in my UI framework, and its very expensive. – Ali Akbarpour Oct 20 '17 at 12:48
  • Why don't you use DTO instead? – O.Badr Oct 20 '17 at 23:54
  • dear @ O.Badr how it can solve this issue ! please provide a solution with sample code. – Ali Akbarpour Oct 21 '17 at 07:17

8 Answers8

1

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

Sachin Gupta
  • 7,805
  • 4
  • 30
  • 45
1

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<>();
Tharsan Sivakumar
  • 6,351
  • 3
  • 19
  • 28
1

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;
}
Hema
  • 988
  • 1
  • 16
  • 38
1

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.

Chris
  • 999
  • 1
  • 6
  • 3
1

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.

Rémi Bantos
  • 1,899
  • 14
  • 27
  • Dear @Rémi Bantos I have bean tried with that annotation there are working perfectly without json, But they are not working with json. If you try to add University side of relation with Student data, it would not be able to add university_id in student table. – Ali Akbarpour Oct 20 '17 at 08:14
  • and in this condition @JsonManagedReference is required and I have to loos some functionality for example i can not have University object in Student side in Json response. – Ali Akbarpour Oct 20 '17 at 08:19
  • I've detailed my answer. You should really consider changing the way you map your ManyToOne-OneToMany relationship with proper Marshalling exclusions for students, as described here. Because, the way you map and the way jackson does the marshalling are correlated. Also I understand you want to include students with eager fetch type and I really advice you not to do that. Instead, you should prefer pagination for example to get all of them. – Rémi Bantos Oct 21 '17 at 20:03
1

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

1

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);
}
Piotr Wilkin
  • 3,446
  • 10
  • 18
  • after adding @JsonRawValue the response in row format is [{ "id" : 1, "stuName" : "stu1", "university" : entity.University@57ab302c }, { "id" : 2, "stuName" : "stu2", "university" : entity.University@57ab302c }] – Ali Akbarpour Oct 21 '17 at 07:12
  • so what is the next steps? – Ali Akbarpour Oct 21 '17 at 07:30
  • Override `toString()` for the `University` class using `ObjectMapper` that returns `mapperObject.writeValueAsString(this)` - that should make it return JSON and not the standard `toString()` representation. – Piotr Wilkin Oct 21 '17 at 11:34
  • Added the `toString()`. – Piotr Wilkin Oct 21 '17 at 11:50
  • it produces error University.toString(University.java:46) at com.fasterxml.jackson.databind.ser.std.RawSerializer.serialize(RawSerializer.java:30) at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:693) at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:675) – Ali Akbarpour Oct 21 '17 at 13:12
1

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.

Daniele Licitra
  • 1,520
  • 21
  • 45