Reputation: 964
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 :
I need both side fetch eagerly(the university side is Ok)
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
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
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
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
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
Reputation: 999
I understand you do not want to include University.students
in your JSON.
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;
}
}
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
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
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
Reputation: 8368
Remove @JsonIdentityInfo
from base class, this is causing university object to serialize only id.
Upvotes: 1