Reputation: 2294
I have a Java API (using Spring Framework) that is used by my front-end to create new parent objects (json) that has a list of new child objects in them like this:
parent = {
children: [
{
childName: 'name1'
},
{
childName: 'name2'
}]
}
As you can see no IDs are there, since I am about to save them, and the database should generate IDs.
The Parent entity has this code in the backend:
@OneToMany(cascade = {CascadeType.ALL}, orphanRemoval = true, fetch = FetchType.EAGER, mappedBy = "parent")
private Set<Child> children = new HashSet<>();
After I call the repository 'save' operation on the Parent I can find the Child elements in the database (so cascade worked), but the 'parentId' column of the child in the database is null and therefore when I GET the parent, its list of children shows up as empty.
I can solve that by doing something like this in my service class:
repository.save(parent);
parent.getChildren().forEach((child) -> child.setParent(parent));
and it works because (I guess) by that time the parent has had an ID generated for it by the database, so the child table can use that ID for its parentId column. It works but it feels like this should be a standard One-to-many relationship and should be handled automatically by the framework in some prettier way than this. So my question is: What am I missing?
Is there some way that Hibernate can automatically populate the child entities 'parentId' column when saving them during the Cascade operation?
Edit: This may in fact be caused by MapStruct that I use to map DTOs to POJOs and back. Not sure yet, but will keep investigating (see relevant link: https://github.com/mapstruct/mapstruct/issues/1068)
Upvotes: 2
Views: 3317
Reputation: 21
Great solution - was sitting with same issue for days, I have a cascading oneToOne, oneToMany etc, setup to generate a pretty extensive xml. I still had to add cascading to the parent.
GrandParent finalGrandParent = grandparent;
grandparent.getParent().getChildren().forEach(child -> child.setParent(finalGrandParent.getParent()));
Upvotes: 0
Reputation: 2294
I figured it out, and it's not a Hibernate issue really, sorry.
The problem is that I use MapStruct to have simpler front-end objects to work with. This separates objects into the POJOs and their respective DTO object. The DTO object doesn't have references to entire other objects, they just have some of the more relevant parts copied into them to make the json simpler.
so ChildDTO for example only has
{
name : 'name',
parentId: '1'
}
and then MapStruct is used when a call comes from front-end to backend, transforming the DTO into a real Java object, finding the parent by id in database ('ish'), and adding it to the child. But it doesn't by default know to add the parent reference to child elements that don't have parentId set, even if they come in as part of a parent (!). And because of this, in the Java Child class - the parent reference is null.
There are at least two solutions:
MapStruct solution:
Add custom mapping code to the MapStruct mappers to manually set the parent reference of all child elements that are sent in as part of a parent. See here
Java solution:
add the parent reference to all children manually after MapStruct has run, but before saving to database:
// MapStruct first maps the DTO to a real Parent object, then:
parent.getChildren().forEach((child) -> child.setParent(parent));
parentRepository.save(parent);
Upvotes: 2
Reputation: 841
Here is some basics on how to create your entities and add children. As you will see, that you will not have to iterate over children and set parent's reference there.
public class Parent implements Serializable {
//bi-directional many-to-one association to Children
@OneToMany(mappedBy="Parent")
private List<Children> children;
public List<Children> getChildren() {
return children;
}
public void setChildren(List<Children> children) {
this.children= children;
}
public Children addChildren(Children child) {
getChildren().add(child);
child.setParent(this);
return child;
}
public Children removeChildren(Children child) {
getChildren().remove(child);
child.setParent(null);
return child;
}
}
Now create parent object and call addChildren to set references properly. Hope it helps.
Upvotes: 0