Reputation: 666
I have the following relation:
I'm trying to map those relations using JPA in Spring Boot, but I'm having some trouble. These are the classes:
Person:
public class Person {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
// Relations
@OneToOne
@JoinColumn(name = "child_id")
private PersonBirth birth;
@OneToMany
@JoinColumn(name = "mother_id")
private List<PersonBirth> motherOf;
@OneToMany
@JoinColumn(name = "father_id")
private List<PersonBirth> fatherOf;
}
PersonBirth:
public class Birth {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "birth_date")
@JsonProperty(value = "birth_date")
private Long birthDate;
// Relations
@OneToOne
@JoinColumn(name = "child_id")
private Person child;
@ManyToOne
@JoinColumn(name = "mother_id")
private Person mother;
@ManyToOne
@JoinColumn(name = "father_id")
private Person father;
}
I'm trying to be able to get a Pearson
and his Birth
data including his mother
and father
. And also be able so get a Person
with its children mapped by fatherOf
or motherOf
. But the way it is right now, it throws stackoverflow when I fetch a Person
who is a mother, because it gets the Birth
data, that contains the child
data (so far what I want), that contains his Birth
data, that contains his mother, that contains her children (and so on). I don't know if what I'm trying to do is possible using this structure or if I'll have to change it... Any suggestions are appreciated.
Upvotes: 0
Views: 3923
Reputation: 319
Just a quick note: Breaking the infinite loop in the JSON serialization using @JsonIgnore
, @JsonManagedReference
and @JsonBackReference
is easy, but also has serious downsides, as these Annotations take away much flexibility when it comes down to what exactly is serialized.
In the scenario described in the question, you e.g. cannot serialize a person with its children without serializing their children and so on. It's a all-or-nothing kind of situation.
That's one of the reasons why you should not return your entity objects in controller methods, but rather so-called data transfer objects (DTOs); using these will allow you to serialize very specifically and on a case-by-case basis, as you can decide on which DTO to use for each controller method.
It is for example possible to serialize a PersonBirth
including all the names of the persons involved without serializing their parents and children.
Upvotes: 0
Reputation: 4180
Just to add what @dunni answered:
In order to allow Jackson works well, one of the two sides of the relationship should not be serialized, in order to avoid the annoying infinite recursive loop that causes stackoverflow error.
Jackson takes the forward part of the reference, for example an attribute of a java class (i.e. List roles in User class), and converts it in a json-like storage format; this is the so-called marshalling process. Then, Jackson looks for the back part of the reference (i.e. List users in Role class) and leaves it as it is, not serializing it. This part of the relationship will be re-constructed during the deserialization (unmarshalling) of the forward reference.
Reference: http://keenformatics.blogspot.com/2013/08/how-to-solve-json-infinite-recursion.html
Upvotes: 1
Reputation: 44555
From your description, i guess the problem occurs when serializing the structure to JSON. If you use Jackson (which is the default with Spring Boot), you can use the @JsonManagedReference
and @JsonBackReference
annotations:
public class Person {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
// Relations
@OneToOne
@JoinColumn(name = "child_id")
@JsonManagedReference("birth")
private PersonBirth birth;
@OneToMany
@JoinColumn(name = "mother_id")
@JsonManagedReference("mother")
private List<PersonBirth> motherOf;
@OneToMany
@JoinColumn(name = "father_id")
@JsonManagedReference("father")
private List<PersonBirth> fatherOf;
}
public class Birth {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(name = "birth_date")
@JsonProperty(value = "birth_date")
private Long birthDate;
// Relations
@OneToOne
@JoinColumn(name = "child_id")
@JsonBackReference("birth")
private Person child;
@ManyToOne
@JoinColumn(name = "mother_id")
@JsonBackReference("mother")
private Person mother;
@ManyToOne
@JoinColumn(name = "father_id")
@JsonBackReference("father")
private Person father;
}
This way, Jackson will not serialize the whole object for the back reference, but just identifiers.
Documentation: http://wiki.fasterxml.com/JacksonFeatureBiDirReferences
Upvotes: 0