JPA Persist parent and child with one to many relationship
Asked Answered
D

5

27

I want to persist parent entity with 20 child entities, my code is below

Parent Class

@OneToMany(mappedBy = "parentId")
private Collection<Child> childCollection;

Child Class

@JoinColumn(name = "parent_id", referencedColumnName = "parent_id")
@ManyToOne(optional=false)
private Parent parent;

String jsonString = "json string containing parent properties and child  collection" 

ObjectMapper mapper = new ObjectMapper();
Parent parent = mapper.readValue(jsonString, Parent.class);

public void save(Parent parent) {
    Collection<Child> childCollection = new ArrayList<>() ;

    for(Child tha : parent.getChildCollection()) { 
        tha.setParent(parent);
        childCollection.add(tha);
    }

    parent.setChildCollection(childCollection);
    getEntityManager().persist(parent);
 }

So if there are 20 child tables then I have to set parent reference in each of them for that I have to write 20 for loops? Is it feasible? is there any other way or configuration where I can automatically persist parent and child?

Defecate answered 4/2, 2016 at 10:4 Comment(3)
This seems more of a JSON question than a JPA question. If your JSON is unmarshalled so that the proper relationships are set, then having the children persisted when saving parent is simply a matter of adding the relevant cascade options to the @OneToMany (assuming your mappings are correct)Thynne
If you are not sending the Child->Parent relationship back, or it isn't set in what gets built from JSON, then yes, you need to manually set it in each child entity. The alternative is to make the relationship uni-directional: remove the mappedby="parent" from the OneToMany and instead specify a JoinColumn. This will cause the OneToMany to set the foreign key in the child table instead of being set by the child's reference to its parent (you then should remove the Child's parent attribute and mapping)Deckhouse
Regarding what @Deckhouse said, just to mention that the recommended way to associate unidirectional @OneToMany is using @JoinTable. From docs: unidirectional one-to-many association on a foreign key is an unusual case, and is not recommended. You should instead use a join table for this kind of association.Oleaceous
N
16

Fix your Parent class:

@OneToMany(mappedBy = "parent")

mappedBy property should point to field on other side of relationship. As JavaDoc says:

The field that owns the relationship. Required unless the relationship is unidirectional.

Also you should explicitely persist Child entity in cycle:

for(Child tha : parent.getChildCollection()) { 
    ...
    getEntityManager().persist(tha);
    ...
}

As Alan Hay noticed in comment, you can use cascade facilities and let EntityManager automatically persist all your Child entities:

@OneToMany(mappedBy = "parent", cascade = CascadeType.PERSIST)

More details about cascades (and JPA itself) you can find in Vlad Mihalcea's blog.

Nightlong answered 4/2, 2016 at 10:14 Comment(5)
Won't he need cascade options this @OneToMany as well?Thynne
but for 20 different child collections i will have to write 20 different for loops?Defecate
@Bharath Reddy, I think yes, cos 20 different child collections is 20 different Parent objects and you will invoke method save(...) 20 times.Nightlong
i had used cascade = CascadeType.PERSIST, but i am getting foregin key as nullDefecate
As I noted above, the JPA part is trivial if the in-memory object model is correct. The correctness of the in-memory model, or otherwise, obviously depends on the structure of your JSON which you have failed to post so it is hard to offer any further assistance. If the JSON is not in a format where the deserializer can set a back reference from Child > Parent then you need to handle this in your code - as you have done - before calling persist (otherwise you will get a null FK as you have seen). If you do want to do this post the JSON. You should be asking a question about JSON and not JPAThynne
H
13

Generally, @JoinColumn indicates that the entity is the owner of the relationship & mappedBy indicates that the entity is the inverse of the relationship.

So, if you are trying like following

@OneToMany(mappedBy = "parent")
private Collection<Child> childCollection;

That means it is inverse of the relationship and it will not set parent reference to its child.

To set parent reference to its child, you have to make the above entity owner of the relationship in the following way.

@OneToMany(cascade = CascadeType.ALL)
@JoinColumn
private Collection<Child> childCollection;

You need not set any child reference because above code will create a column in the child table.

Histogram answered 2/5, 2017 at 5:11 Comment(2)
it's not a better way as it called Unidirectional @OneToMany and then Hibernate will create in twice more SQL requests to DB vladmihalcea.com/2017/03/29/…Aldose
Your answer is not true. You can refer to this link : vladmihalcea.com/…Monotonous
N
10

As pointed out in the comments you must take care of the object graph consistency with child/parent relationship. This consistency won't come free when JSON is coming directly from i.e. a POST request.

You have to annotate the parent and child field with @JsonBackReference and @JsonManagedReference.

Parent class:

@OneToMany(mappedBy = "parentId")
@JsonBackReference
private Collection<Child> childCollection;

Child class:

@JoinColumn(name = "parent_id", referencedColumnName = "parent_id")
@ManyToOne(optional=false)
@JsonManagedReference
private Parent parent;

Similar question with answer is here

Furthermore, if you use @JsonBackReference/@JsonManagedReference on javax.persistence annotated classes in combination with Lombok's @ToString annotation you will incur in stackoverflow error.

Just exclude childCollection and parent field from the @ToString annotation with @ToString( exclude = ...)

The same will happen with Lombok's generated equals() method (@Data, @EqualsAndHashCode). Just implements those methods by hand or to use @Getter and @Setter annotations only.

Navy answered 22/8, 2016 at 10:15 Comment(2)
This was a great help but you switched things, you should add the JsonManagedReference on the Parent and the JsonBackReference on the child. Do not forget to remove JsonIgnore case you have, because it gives you "Unsupported media type" in the requestSentimentalism
The @JsonBackReference annotation may not be used on collections of child objects. It is the other way around. Use @JsonManagedReference with @OneToManyon the list of child properties in the parent class, and @JsonBackReference with @ManyToOne on the property of the child entity class that points back at the parent.Standup
H
2

I would let the parent persist it's own children

package com.greg;

import java.util.ArrayList;
import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;

@Entity(name = "PARENT")
public class Parent {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column(name = "NAME")
    private String name;

    @Column(name = "DESCRIPTION")
    private String description;

    @OneToMany(cascade = CascadeType.ALL, fetch=FetchType.EAGER)
    @JoinColumn(name = "parent", referencedColumnName = "id", nullable = false)
    private List<Child> children = new ArrayList<Child>();

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public List<Child> getChildren() {
        return children;
    }

    public void setChildren(List<Child> children) {
        this.children = children;
    }

}
Hebetic answered 4/2, 2016 at 10:24 Comment(2)
Is using the FetchType.EAGER necessary in the case where we only want the parent to persist the children as well ?Hardden
@Hardden no, think I did it for testing reasonsHebetic
B
0

I am using lombok to generate getter and setter properties on my entity classes. I was also facing issue of NULL referenceID on child entity when I was trying to save parent Entity having child. On my parent entity when I add children then I set "this" reference of parent on child. In my example, I have User table and Address table where a User can have many addresses.
I have created domain classes as below.

e.g address.setUser(this);

package com.payment.dfr.entities;

import lombok.Data;

import javax.persistence.*;
import java.math.BigInteger;
@Entity
@Data
@Table(name="User")
public class User {

    @Id
    @GeneratedValue
    private BigInteger RecordId;
    private String Name;
    private String Email;

    @Getter(AccessLevel.NONE)
    @Setter(AccessLevel.NONE)
    @OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
    private List<Address> addresses = new ArrayList<>();

    public void addAddress(Address address){
        address.setUser(this);
        addresses.add(address);
    }
    
}

@Entity
@Data
@Table(name="UserAddress")
public class Address {

    @Id
    @GeneratedValue
    private BigInteger RecordId;
    private String AddressLine;
    private String City;

    @ManyToOne(cascade = CascadeType.ALL)
    @JoinColumn(name="UserId")
    private User user;

}


This is how I save user with address 

    User newUser = new User();
    newUser.setName("Papa");
    newUser.setEmail("[email protected]");
    Address address1 = new Address();
    address1.setAddressLine("4401 Central Ave");
    address1.setCity("Fremont");
    newUser.addAddress(address1);
    Address address2 = new Address();
    address2.setAddressLine("4402 Central Ave");
    address2.setCity("Fremont");

    newUser.addAddress(address2);
    User user1 = userRepository.save(newUser);
    log.info(user1.getRecordId().toString());
Baier answered 19/3, 2020 at 7:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.