How to save parent and child in one shot (JPA & Hibernate)
Make sure that your method is Transactional. you can make method Transactional using @Transactional
annotation on top of method signature.
Here's the list of rules you should follow, in order to be able to store a parent entity along with its children in a one shot:
- cascade type
PERSIST
should be enabled (CascadeType.ALL
is also fine) - a bidirectional relationship should be set correctly on both sides. E.g. parent contains all children in its collection field and each child has a reference to its parent.
- data manipulation is performed in the scope of a transaction. NO AUTOCOMMIT MODE IS ALLOWED.
- only parent entity should be saved manually (children will be saved automatically because of the cascade mode)
Mapping issues:
- remove
@Column(name="id")
from both entities - make setter for
cartItems
private. Since Hibernate is using its own implementation of theList
, and you should never change it directly via setter - initialize you list
private List<CartItem> cartItems = new ArrayList<>();
- use
@ManyToOne(optional = false)
instead ofnullable = false
inside the@JoinColumn
- prefer
fetch = FetchType.LAZY
for collections it's better to use helper method for setting relationships. E.g. class
Cart
should have a method:public void addCartItem(CartItem item){ cartItems.add(item); item.setCart(this); }
Design issues:
- it's not good to pass DTOs to the DAO layer. It's better to do the conversion between DTOs and entities even above the service layer.
- it's much better to avoid such boilerplate like method
save
with Spring Data JPA repositories
For bidirectional relation act like below:
- Set cascade to persist or All
- Remove mappedBy attribute in @OnToMany if there is
- Write @JoinCloumn at both sides (otherwise it creates Join Table) with the same name
- Remove (nullable = false) in @JoinColumn (because Hibernate first inserts the parent record then inserts child records and after all, updates the foreign key in child records)
Here is the sample code:
public class Parent {
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = "fk_parent")
private List<Child> children;
}
public class Child {
@ManyToOne
@JoinColumn(name = "fk_parent")
private Parent parent;
}