Issue
I'm developing a REST API with Spring Boot and I want to use my domain class as my request object (DTO). However, if I try to save an object with child entities, hibernate isn't able to create the correct relations.
Example
Assume the model of a Survey. Surveys contain Pages.
class Survey {
@Id
@GenericGenerator(name="uuid", strategy = "path.to.package.UUIDGenerator")
@GeneratedValue(generator = "uuid")
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
private UUID id;
private String name;
@OneToMany(cascade = {CascadeType.ALL}, mappedBy = "survey")
private List<Page> pages = new ArrayList<>();
}
class Page {
@Id
@GenericGenerator(name="uuid", strategy = "path.to.package.UUIDGenerator")
@GeneratedValue(generator = "uuid")
private UUID id;
private String label;
@ManyToOne(cascade = {CascadeType.ALL})
@JoinColumn(name = "survey_id")
private Survey survey;
}
If I now want to create a new survey via the controller, I would do something like this
@PostMapping
public HttpEntity<SurveyDto> createSurvey(@RequestBody Survey request) {
var s = surveyService.createSurvey(request);
URI self = UriComponentsBuilder.fromPath(BASE_URL + "/{id}")
.uriVariables(Map.of("id", s.getId()))
.build().toUri();
return ResponseEntity.created(self).body(s);
with a request that would look like this:
{
"name": "survey1",
"pages": [
{
"label": "First Page"
},
{
"label": "Second Page"
}
]
}
The JSON parser parses it correctly, however the references are not correct, as in: page.getSurvey()
would be null
. Therefore, hibernate can't set the relations (or at least that's what i think is happening)
My Question now is, if it is possible to achieve something, where I can just post a Survey as JSON and then just save that object, without having to manually map the JSON to a new Survey object where I manually set the pages array, since that would be a lot of mapping I'd have to do because of multiple child entities.
(First time posting a question, so if I missed any important information, feel free to request in the comments)
Solution
@M. Deinum had the right clue, by adding @JsonManagedReference
to the list and @JsonBackReference
to the survey field, Jackson knew how to correctly parse the object and therefore created the correct references.
Updated Example
class Survey {
@Id
@GenericGenerator(name="uuid", strategy = "path.to.package.UUIDGenerator")
@GeneratedValue(generator = "uuid")
private UUID id;
private String name;
@OneToMany(cascade = {CascadeType.ALL}, mappedBy = "survey")
@JsonManagedReference
private List<Page> pages = new ArrayList<>();
}
class Page {
@Id
@GenericGenerator(name="uuid", strategy = "path.to.package.UUIDGenerator")
@GeneratedValue(generator = "uuid")
private UUID id;
private String label;
@ManyToOne(cascade = {CascadeType.ALL})
@JoinColumn(name = "survey_id")
@JsonBackReference
private Survey survey;
}
Also, if you have multiple relations like these you have to give your references names with @JsonManagedReference(value = "survey-page")
and @JsonBackReference(value = "survey-page)
Answered By - Christoph Kainz
Answer Checked By - Senaida (JavaFixing Volunteer)