saving mapped collection in new entity - java

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.

Related

Parent child issue hibernate jpa

Consider I have 2 entity
#Entity(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
public Long id;
#OneToMany(cascade = CascadeType.ALL)
public List<Child> children = new ArrayList<>();
}
and
#Entity
class Child {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
public Long id;
#ManyToOne
public Parent parent;
}
how can I create Child instance with parentId
without call findById(Long parentId) i.e.
Child createChild(parentId) {
Child child = new Child();
child.parent = //parent.findById(parentId); I don't wanna go to database
//for nothing if in this spot anyway will be parentId in database
return child;
}
I thought it can be done with quare but hql don't have
INSERT .... VALUE .., so I'm here, appreciate any help.
If it's don't have any sense due to architecture,
please explain, it's be a great help.
No need to create new object in
public List<Child> children = new ArrayList<>();
just write
#OneToMany(targetEntity = Child.class, cascade = CascadeType.ALL)
public List<Child> children;
It is not a big problem that you will call parent.findById(parentId);
Hibernate caches some of the requests especially when used findById. You can see this answer link
The only thing to note is that you should not override findById in the repository, or if you do you should added it to the jpa cache.
EntityManager#getReference(Class<T> entityClass, Object primaryKey) is your friend here.
entityManager.getReference(Parent.class, parentId); returns an entity proxy. It can be used to improve the performance of the write operations since there will be no database call unless you access the fields of the returned entity.

SpringBoot JPA Many2One, One2Many not work properly in two way

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;

JPA Hibernate cascade type for child of child

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.

Parent child relation without cascade

Are there case with is usefull to not use cascade in a parent - child relation?
Actually I use alway cascade and i would like to know if that could be interesting to avoid to use them.
#Entity
public class Lodger{
#OneToMany
private List<Bail> bailList;
...
}
#Entity
public class Bail {
#OneToMany
private List<Rent> rents;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "lodger_id")
private Lodger lodger;
#OneToOne
private Room room;
}
#Entity
private class Rent{
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "bail_id")
private Bail bail;
#OneToMany
private List<RoomPayment> roomPayment;
}
#Entity
private class RoomPayment{
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "rent_id")
private Rent rent;
}
So all relation seem to be composition. If i create a new RoomPayment, is it better to do:
roomPayment.setRent(rent);
roomPaymentDao.save(roomPayment);
rent.getRoomPayment().add(roomPayment);
rentDao.save(rent);
I think you are mixing up compositions and parent - child relationships.
Composition
From the wikipedia definition:
It is ... called composite, if the objects it refers to are really its parts, i.e. have no independent existence
Composite relationships are always modeled with a CascadeType.ALL or [CascadeType.PERSIST, CascadeType.REMOVE] (the later for huge objects that you would like to update only if necessary). If you don't use a cascade here, you don't have a composition.
Parent - child relationships
There are different relationships that could be called parent - child relation. I guess you are talking about something like a directory association:
#Entity
public Directory {
#ManyToOne
private Directory parent;
#OneToMany(mappedBy = "parent")
private Collection<Directory> children;
}
In that case you will often need to manage the association manually, because you don't want to update the whole tree if you change an attribute of the root directory.
You could add CascadeType.REMOVE to the children association - but it is better to delete subdirectories explicitly to prevent harmful mistakes (like the explicit -r for rm).

Remove parent entity without removing children (jpa)

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.

Categories

Resources