I have created a program by using JPA and SpringBoot, the database is Postgresql, i have two entities: Parent and Child:
#Entity
#Table(name = "parent")
public class Parent {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
private Set<Child> children = new HashSet<>();
}
And the Child entity:
#Entity
#Table(name = "child")
public class Child {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#ManyToOne
#JoinColumn(name = "parent")
private Parent parent;
}
Then in the Application, i have autowired two repositories to do some tests:
It works when i do:
Child child1 = new Child("Lucas", new Date(2012, 12,12));
Parent parent1 = new Parent("Jack", "Bauer");
child1.setParent(parent1);
childRepository.save(child1);
In the table Child, the parent id is set correctly.
But if i create from another side, it doesn't work:
Child child1 = new Child("Lucas", new Date(2012, 12,12));
Parent parent1 = new Parent("Jack", "Bauer");
childRepository.save(child1);
parent1.getChildren().add(child1);
parentRepository.save(parent1);
No error appears, and no relationship is updated in the table Child
Can you tell me why?
Thank you.
Bidirectional #OneToMany:
The best way to map a #OneToMany association is to rely on the #ManyToOne side to propagate all entity state changes:
Parent Class:
#OneToMany(
mappedBy = "post",
cascade = CascadeType.ALL,
orphanRemoval = true
)
private List<Child> childs = new ArrayList<>();
//Constructors, getters and setters removed for brevity
public void addChild(Child child) {
childs.add(child);
comment.setChild(this);
}
public void removeChild(Child child) {
childs.remove(child);
child.setPost(null);
}
Child Class:
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "parent_id")
private Parent parent;
The #ManyToOne association uses FetchType.LAZY because, otherwise, we’d fall back to EAGER fetching which is bad for performance
The parent entity, features two utility methods (e.g. addChild and removeChild) which are used to synchronize both sides of the bidirectional association. You should always provide these methods whenever you are working with a bidirectional association as, otherwise, you risk very subtle state propagation issues.
For test :
Parent parent1=new Parent();
// set datas into parent1 and to put childs we can use the utility method addChild
parent1.addChild(new Child(datas...))
parent1.addChild(new Child(datas...)) //etc
parentRepository.save(parent1);
The question you have is why does the Cascade operation fail to work when you add a Child to the Parent and have a cascade annotation on the Parent.
Generally the owner of the relationship, in this case the Child as indicated by the mappedBy="parent" annotation, is responsible for persisting the relation. You have demonstrated this with the unidirectional mapping for the Child -- done with the ManyToOne annotation.
Child child = new Child();
Parent parent = new Parent();
child.setParent(parent);
parentRepo.save(parent);
childRepo.save(child);
You then you tried the same thing with the bidirectional mapping in the Parent -- done with the OneToMany annotation. Since this annotation includes the mappedBy="parent" annotation it is not the owner and normally anything added to the Set<Child> children would be ignored. However you added the cascade = CascadeType.ALL annotation so this overrides the ownership settings and allows the Parent entity to persist relations for a subset of operations and specific conditions as determined by the CascadeType value.
But how is the parent to know which children to persist? I assume that it looks at whether the child instance has already been persisted. If it has, then no cascade operation would be needed. When you persisted the child instance yourself you circumvented the cascade operation.
Child child = new Child();
Parent parent = new Parent();
Set<Child> children = new HashSet<>();
childRepo.save(child);
children.add(child);
parent.setChildren(children);
parentRepo.save(parent);
This particular code give me an error because the child instance has been saved and detached and then asked to be saved again. The error condition doesn't always happen - I think depending on whether the parent is new or has been retrieved from the db.
org.hibernate.PersistentObjectException: detached entity passed to persist:
So if you want the Parent entity to do a cascade you have to pass it a Child instance that has not been already saved. Note that you still have to set the child's parent in order for the relation to be created otherwise the parent will persist a parentless child.
Child child = new Child();
Parent parent = new Parent();
child.setParent(parent);
Set<Child> children = new HashSet<>();
children.add(child);
parent.setChildren(children);
parentRepo.saveAndFlush(parent);
And this works fine for me. Note that I create the Set of children myself instead of creating it every time a Parent entity is instantiated. Generally you will be doing queries against a database much more often then updates and for every query the JPA provider will put its own Collection class into the children property of the Parent and so the set you instantiated will generally end up on the garbage heap -- somewhat inefficient.
#Entity
public class Child {
#Id #GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#ManyToOne
private Parent parent;
#Entity
public class Parent {
#Id #GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval=true)
private Set<Child> children;
Related
I was looking for documentation or answer how will the cascade work for child of child , example :
public class Parent{
#OneToMany(fetch = FetchType.EAGER,mappedBy = "parent",cascade = CascadeType.ALL)
private List<Child> child;
}
public class Child{
#OneToMany(mappedBy="child")
private List<AnotherChild> anohterChild;
}
#ManyToOne
private Parent parent;
}
now the question, will the cascade operation applied on "Child" from parent class apply to "AnotherChild" ?
in other words if I persist "Parent" Object will it persist "AnotherChild" ?
If you persist your parent, only those childs which are in the child-list of your parent-class get persisted but not the list of AnotherChilds in your child-class.
If you wish to persist them too just cascade it too:
public class Child{
#OneToMany(mappedBy="child", cascade = CascadeType.PERSIST)
private List<AnotherChild> anohterChild;
#ManyToOne
private Parent parent;
}
And just use CascadeType.ALLwhen you really need it, because this cascade-type includes more than just persisting. As it is explained in the following picture, CascadeType.ALL includes all other cascade-types including the cascade-type "remove" which means that, when your parent-object gets removed, all other child-objects get removed too.
I am using the Hibernate persistence API. I have two entities, you can check the Parent of these: (The Child entity does not contain any reference to the Parent - because it is not necessary business logically.)
#Entity(...)
public class ParentEntity implements Serializable {
#Id
private Integer id;
// ...
#OneToMany(fetch = FetchType.LAZY)
#org.hibernate.annotations.Cascade({ org.hibernate.annotations.CascadeType.ALL }) // losing JPA's portability
#JoinColumn(name = "parent_id", referencedColumnName = "id")
private Set<ChildEntity> children;
// ...
}
When I persist a new Parent entity, a DB trigger is executed. This trigger inserts some Children by default values. But after persisting, the children are not in the collection. How can I solve this? Here is my Dao method:
#Transactional(readOnly = false)
#Override
public Parent addParent(String name) {
Parent temp = new Parent(name);
entityManager.persist(temp);
return entityManager.find(Parent.class, temp.getId()); // return temp;
}
Thank you in advance very much for everything.
Parent temp = new Parent(name);
entityManager.persist(temp);
If i get you properly,
You are creating a new parent object (without any child entity) and persisting it.
So a parent object without any child is persisted in DB.
When you do
entityManager.find(Parent.class, temp.getId());
You can't expect to have a parent class with child entities.
Since you are persisting the child object by a db trigger, there must be some way to put parent_id (tem.getId()/parent.getId()) in them
There is no other to way to find child objects of the persisted parent.
Here are my entities:
#Entity
public class Parent {
#Id
#Column(name = "ID")
private Long id;
#OneToMany(cascade = {CascadeType.ALL}, mappedBy = "parent")
private Set<Child> childs = new HashSet<Child>();
...
}
The child:
#Entity
public class Child {
#Id
#Column(name = "ID")
private Long id;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name="PARENTID", nullable = false)
private Parent parent;
...
}
I want to perform following operations:
Delete child entity from parent (not the parent itself).
Add new child entity to parent (parent.setChild(child)).
Now save the child entity in to DB and update parent accordingly.
This is what I tried but it raises ConstraintViolationexception for parent:
entityManager.remove(parent.getChild())
parent.setChild(new Child())
entityManager.merge(parent);
How can I fix this?
The 'old' child probably still references the parent, while the new child does not. Both is an issue.
In addition to removing an old child, you should set the parent reference of the child instance to null.
In addition to adding the new child to the parent, you will need to add the parent to the child in order to provide the foreign key.
Do not cascade from the many side (child) to the one side (parent). The behavior for this type of cascades is undefined and might work in an unexpected way.
EDIT: what the JPA 2.0 spec has to say:
Note that it is the application that bears responsibility for maintaining
the consistency of runtime relationships—for example, for
insuring that the “one” and the “many” sides of a bidirectional
relationship are consistent with one another when the application
updates the relationship at runtime.
Modify the relation in the parent as follows:
#OneToMany(cascade = {CascadeType.ALL}, orphanRemoval=true, mappedBy = "parent")
Just set the new child to the parent and merge the parent. Now the children referencing earlier becomes orphans and JPA automatically deletes those while committing the transaction.
Thanks,
JK
I'm trying to remove list of parent without removing children
The parent :
#Entity
public class Parent {
#Id
#Column(name = "PARENTID")
private Long id;
#OneToMany(cascade = {CascadeType.ALL}, mappedBy = "parent")
private Set<Child> childs = new HashSet<Child>();
...
}
The child:
#Entity
public class Child {
#Id
#Column(name = "CHILDID")
private Long id;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name="PARENTID", nullable = false)
private Parent parent;
...
}
What i did is to update all children using HQL query, and then delete the list of parents using HQL query as well.
The problem is that this way is too heavy, is there any simple solution using JPA ?
you could set your Cascade in the following section to not delete
#OneToMany(cascade = {CascadeType.ALL}, mappedBy = "parent")
private Set<Child> childs = new HashSet<Child>();
by editing the annotation as follows:
#OneToMany(cascade = {CascadeType.MERGE, CascadeType.PERSIST}, mappedBy = "parent")
and whatever other CascadeType options you need ( see CascadeType Enums). This will make it so that when you delete the parents, the children won't be deleted as well.
The mapping as it is does not allow for a simple deletion of parents with their children. It does not support having a Child without a Parent (nullable = false).
You either need to
set the parent id to a 'surrogate' Parent before removal of the parents. You can do it by a bulk update or by fetching the parents that are about to be deleted, iterate over the children and reset the parent references. Whether you use bulk updates or object manipulation depends on how you would remove the parents. If you remove the parents with a bulk query, use a bulk query for the children as well. In general I would use the object approach as the safer one. The bulk query is more compact.
drop the nullability constraint and change the provided cascade. Remove the REMOVE cascade from the #OneToMany mapping and you can remove parents as you like.
I've read the documentation and thought I'd be able to do the following....
map my classes as so (which does work)
#Entity
public class ParentEntity
{
...
#OneToMany(mappedBy = "parent")
private List<ChildEntity> children;
...
}
#Entity
public class ChildEntity
{
...
#Id
#Column
private Long id;
...
#ManyToOne
#NotFound(action = NotFoundAction.IGNORE)
#JoinColumn(name = "parent_id")
private ParentEntity parent;
...
}
.. but i want to be able to insert into both tables in one go and thought this would work:
parent = new ParentEntity();
parent.setChildren(new ArrayList<ChildEntity>());
ChildEntity child = new ChildEntity();
child.setParent(parent);
parent.getChildren().add(child);
session.persist(parent);
Can anyone tell me what i'm missing?
Do i need to save the parent first, then add the child and save it again?
thanks.
You have to add #OneToMany(cascade=CascadeType.PERSIST). You can also have CascadeType.ALL which includes persist, merge, delete...
Cascading is the setting that tells hibernate what to do with collection elements when the owning entity is persisted/merged/deleted.
By default it does nothing with them. If the respective cascade type is set, it invokes the same operation for the collection elements that were invoked for the parent.