JPA: Reverse cascading delete - java

#Entity
public class Parent {
#Id
#GeneratedValue(strategy=GenerationType.TABLE)
int id;
#OneToMany(cascade=CascadeType.REMOVE)
List<Item> children = new ArrayList<Child>();
}
#Entity
public class Child {
#Id
#GeneratedValue(strategy=GenerationType.TABLE)
int id;
}
As you can see above, I have a OneToMany-Relation between parent and child. If I delete an instance of parent, all children are also deleted. Is there a way to get this working the other way round as well?
Parent p = new Parent();
Child c = new Child();
p.children.add(c);
EntityManager.persist(p);
EntityManager.persist(c);
EntityManager.remove (c);
This code runs without exception, but when I load p the next time, there is a new child attached.

If you want deletes to work from both sides, you need to define a bi-directional relationship between Parent and Child:
// in Parent
#OneToMany(cascade=CascadeType.REMOVE, mappedBy="parent")
List<Item> children = new ArrayList<Child>();
// in Child
#ManyToOne
Parent parent;

Related

JPA Cannot access parent class from list of child class

I'm trying to understand jpa relationships.
I have a #OneToMany relationship between two classes.
public class Parent {
#Id
private Long id;
#OneToMany(mappedBy="parent", fetch=FetchType.LAZY)
private List<Child> children;
}
public class Child {
#Id
private Long id;
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="id")
private Parent parent;
}
I'm would like to get the Parent Id from the list of Child but the Parent is null
List<Child> children = repository.findAll();
Parent parent = children.get(0).getParent(); //for testing, this is null
But when I only get one Child, the Parent is not null
Child child = repository.findOne();
Parent parent = child.getParent() // this is not null
I'm not sure why Parent is null when using findAll();

A different object with the same identifier using Spring data jpa with Hibernate

I have checked different sources but none solve my problem, such as:
https://coderanch.com/t/671882/databases/Updating-child-DTO-object-MapsId
Spring + Hibernate : a different object with the same identifier value was already associated with the session
My case: I have created 2 classes, 1 repository as below:
#Entity
public class Parent{
#Id
public long pid;
public String name;
#OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
public List<Child> children;
}
-------------------------------------------------------------------
#Entity
public class Child{
#EmbeddedId
public PK childPK = new PK();
public String name;
#ManyToOne
#MapsId("parentPk")
#JoinColumn(name = "foreignKeyFromParent")
public Parent parent;
#Embeddable
#EqualsAndHashCode
static class PK implements Serializable {
public long parentPk;
public long cid;
}
}
------------------------------------------------------------------------
public interface ParentRepository extends JpaRepository<AmazonTest, Long> {
}
Where Parent and Child has One To Many relationship.
In my main method:
public static void main(String[] args) {
#Autowired
private ParentRepository parentRepository;
Parent parent = new Parent();
parent.pid = 1;
parent.name = "Parent 1";
Child child = new Child();
List<Child> childList = new ArrayList<>();
child.childPK.cid = 1;
child.name = "Child 1";
childList.add(child);
parent.children= childList;
parentRepository.save(parent);
parentRepository.flush();
}
When I run the application for the first time, data can successfully saved to the database. But if I run it again, it gives error "Exception: org.springframework.dao.DataIntegrityViolationException: A different object with the same identifier value was already associated with the session".
I was expecting if the data is new, it will update my database, if data is the same, nothing happen. What's wrong with my code.
If I made parent stand alone (without any relationship with the child). It will not give any error even I rerun the application.
Edited: However, if I use the below implementation with simple primary key in Child Entity, it will work as I expected. I can rerun the application without error. I can also change the value, such as the child.name and it will reflect in database.
#Entity
public class Parent{
#Id
public long pid;
public String name;
#OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
public List<Child> children;
}
-------------------------------------------------------------------
#Entity
public class Child{
#Id
public long cid;
public String name;
#ManyToOne
#JoinColumn(name = "foreignKeyFromParent")
public Parent parent;
}
------------------------------------------------------------------------
public interface ParentRepository extends JpaRepository<AmazonTest, Long> {
}
-------------------------------------------------------------------------
public static void main(String[] args) {
#Autowired
private ParentRepository parentRepository;
Parent parent = new Parent();
parent.pid = 1;
parent.name = "Parent 1";
Child child = new Child();
List<Child> childList = new ArrayList<>();
child.cid = 1;
child.name = "Child 1";
childList.add(child);
parent.children= childList;
parentRepository.save(parent);
parentRepository.flush();
}
Well, parent.pid is your database primary key. You can only save one recordset to the database with id=1. This is expected behaviour.
Maybe make yourself familiar with #GeneratedValue in order to avoid setting the id yourself.
Before full explaination a little note: try to post code that actually compiles and works as advertised.
Your main() does not compile,
you dont set up full relation between Parent and Child.
Also try to explicitely demarcate transactions in the posted example.
How your code works
You are calling save on a repository. Underneath, this method calls entityManager.merge() as you have set an id yourself. Merge calls SQL Select to verify if the object is there, and subsequently calls SQL insert or update for the object. (The suggestions that save with the object with id that exists in db are wrong)
In the first run, the object is not there.
you insert parent
merge is cascaded and you insert child (lets call it childA)
In the second run
merge selects parent (with childA)
We compare if new parent is already in the session.
This is done in SessionImpl.getEntityUsingInterceptor
parent is found
merge is cascaded to the child
again, we check if the object is already in the session.
Now the difference comes:
Depending on how you set up the relation between child and parent, the child may have an incomplete PK (and rely on filling it from the relation to parent annotated with #MapsId). Unfortunately, the entity is not found in the session via the incomplete PK, but later, when saving, the PK is complete, and now, you have 2 confilicting objects with the same key.
To solve it
Child child = new Child();
child.parent = parent;
child.childPK.cid = 1;
child.childPK.parentPk = 1;
This also explains why the code works when you change the PK of Child to a long - there is no way to screw it up and have an incomplete PK.
NOTE
The solution above makes mess with orphans.
I still think that the original solution is better as the orphans are removed.
Also, adding updated soution to original solution is a worthwhile update.
Removing entire list and re-inserting it is not likely perform well under load.
Unfortunalely it removes the list on the first merge of the parent, and re-adds them on the second merge of the parent. (This is why clear is not needed)
Better still, just find the parent entity and make the updates on it (as other answers suggest).
Even better, try to look at the solution and add / replace only specific children of the parent, not lookig at the parent and its children ollection. This will be likely most performant.
Original Solution
I propose the following (note that total replacement of the chilren list is not allowed, as it is a hibernate proxy).
#OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true)
public List<Child> children = new ArrayList<>();
#SpringBootTest
public class ParentOrphanRepositoryTest {
#Autowired
private ParentOrphanRepository parentOrphanRepository;
#Test
public void testDoubleAdd() {
addEntity();
addEntity();
}
#Transactional
public void addEntity() {
Parent parent = new Parent();
parent.pid = 1;
parent.name = "Parent 1";
parent = parentOrphanRepository.save(parent);
Child child = new Child();
List<Child> childList = new ArrayList<>();
child.parent = parent;
child.childPK.cid = 1;
child.name = "Child 1";
childList.add(child);
// parent.children.clear(); Not needed.
parent.children.addAll(childList);
parentOrphanRepository.save(parent);
parentOrphanRepository.flush();
}
}

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;

how to select child in one to many relation in jpa

I want to select parent with child that i want.
But when I select my parent I have to show all the childs
How can i do that?
Example:
public class parent{
private Integer id;
#OnetoMany
#JoinColumn(name="parentId")
private List<child> children;
}
public class child{
private Integer id;
private Integer parentId;
}
findByIdAndchildType(Integer id, String type)
I want to see : parent(id) - > child (type)
But i can see parent(id) - > child(othertype), child(othertype1), child(type)
It sounds to me that you're trying to get a bi-directional relation. This is possible by adding the mapping to both sides of the relation.
For example, add a #ManyToOne mapping to the Child entity. Be aware that you should probably remove your parentId field since now you can access it by using child.getParent().getId().
#Entity
public class Child {
#Id
private Integer id;
#ManyToOne
#JoinColumn(name = "parentId")
private Parent parent;
// Remove parentId field
// Getters + Setters ...
}
NOTE: If you want to keep the parentId field, you'll have to choose which two of the mappings (getParentId() or getParent().getId()) you want to use for inserting and updating entities. The other field should have both insertable = false and updatable = false.
The next step is to change the #OneToMany mapping to use mappedBy:
#Entity
public class Parent {
#Id
private Integer id;
#OneToMany(mappedBy = "parent") // Change this
private List<Child> children;
// Getters + Setters ...
}
If you want to retrieve a specific child with its parent, you can now create a repository for Child entities:
public interface ChildRepository extends JpaRepository<Child, Integer> {
}
After that, you can get a specific child by using:
Optional<Child> child = repository.findById(123); // 123 is the ID of the child in this case
Optional<Parent> parent = child.map(Child::getParent);
With Spring boot 1.x that would be:
Child child = repository.findOne(123);
Parent parent = null;
if (child != null) {
parent = child.getParent();
}

How can I fill the complete entity after persist?

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.

Categories

Resources