I've created two entities with a OneToMany relationship but when I remove the parent manually from the database the children remain. I've tried different solutions but nothing seems to work. What am I doing wrong?
#Entity
#Table(name = "PARENT")
public class Parent implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
Integer id;
#OneToMany(cascade = CascadeType.PERSIST, orphanRemoval = true)
#JoinColumn(name = "parent_id")
public List<Child> children = new ArrayList<Child>();
}
#Entity
#Table(name = "CHILD")
public class Child implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
public Integer id;
}
I've tried different solutions like the one attached below, but for some reason when i delete the parent manually through commands the children remain after the parent was removed.
What is the difference between cascade and orphan removal from DB?
I suggest you may recheck your database tables. There should be a 'PARENT_ID' column in the 'CHILD' table. Which will prevent deleting a parent without deleting respective children by giving a foreign key violation error related to your database.
Please check your table structure for tables PARENT and CHILD in the database. There should be a foreign key reference to parent_id in the CHILD table and on delete cascade property set.
As per your models, there is no relationship from child to parent and hence the delete of parent is not cascading the child.
You can either create bidirectional relationship by adding #ManyToOne relationship from child to parent
or use #OnDelete(action = OnDeleteAction.CASCADE) property after specifying unidirectional #ManyToOne relationship from child side.
Related
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;
This has already been asked a number of times, but I don't find any good answers so I'll ask it again.
I have parent-children unidirectional relation as follows:
#Entity
#Table(name = "PARENT")
public class Parent {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private Long parentId;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST)
#JoinTable(name = "CHILD", joinColumns = #JoinColumn(name = "parent_id"), inverseJoinColumns = #JoinColumn(name = "ID"))
private List<Child> children;
}
#Entity
#Table(name = "CHILD")
public class Child {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private Long id;
#Column(name = "PARENT_ID")
private Long parentId;
//some other field
}
I create an instance of the parent, assign a list of children to it and try to persist it:
Parent p = new Parent();
List<Child> children = new ArrayList<Child>();
Child c = new Child();
children.add(c);
p.addChildren(children);
em.merge(p);
When the code runs, I get the following exception:
MySQLIntegrityConstraintViolationException: Cannot add or update a
child row: a foreign key constraint fails
(testdb.child, CONSTRAINT parent_fk
FOREIGN KEY (parent_id) REFERENCES parent (id) ON
DELETE NO ACTION ON UPDATE NO ACTION)
I'm assuming this is due to the fact that the parent is not fully inserted when the child is being attempted to be inserted.
If I don't add the children to the parent, the parent gets inserted just fine.
I tried switching the GeneratedValue generation strategy but that did not help.
Any ideas how to insert the parent & the children at the same time?
Edit: Even if I persist the parent first, I'm still getting the same error. I determined it's because the parent_id is not set in the child; the child is default constructed and thus the parent_id is set to 0 which does not exist thus the foreign key constraint validation.
Is there a way to get jpa to automatically set the parent_id of the children that are assigned to the parent instance?
Your relationship does not have to be bi-directional. There is some mis-information in the comments here.
You also said that you had added the field "parentId" into the Child entity because you assumed that JPA needs to "know" about the parent field so that it can set the value. The problem is not that JPA does not know about the field, based on the annotations that you have provided. The problem is that you have provided "too much" information about the field, but that information is not internally consistent.
Change your field and annotation in Parent to:
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST)
#JoinColumn(name = "parent_id")
private List<Child> children;
Then remove the "parentId" from the Child entity entirely.
You had previously specified a JoinTable annotation. However, what you want is not a JoinTable. A JoinTable would create an additional third table in order to relate the two entities to each other. What you want is only a JoinColumn. Once you add the JoinColumn annotation onto a field that is also annotated with OneToMany, your JPA implementation will know that you are adding a FK into the CHILD table. The problem is that JPA has a CHILD table already defined with a column parent_id.
Think of it that you are giving it two conflicting definitions of both the function of the CHILD table and the parent_id column. In one case, you have told you JPA that it is an entity and the parent_id is simply a value in that entity. In the other, you have told JPA that your CHILD table is not an entity, but is used to create a foreign key relationship between your CHILD and PARENT table. The problem is that your CHILD table already exists. Then when you are persisting your entity, you have told it that the parent_id is explicitly null (not set) but then you have also told it that your parent_id should be updated to set a foreign key reference to the parent table.
I modified your code with the changes I described above, and I also called "persist" instead of "merge".
This resulted in 3 SQL queries
insert into PARENT (ID) values (default)
insert into CHILD (ID) values (default)
update CHILD set parent_id=? where ID=?
This reflects what you want perfectly. The PARENT entry is created. The CHILD entry is created, and then the CHILD record is updated to correctly set the foreign key.
If you instead add the annotation
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST)
#JoinColumn(name = "parent_id", nullable = false)
private List<Child> children;
Then it will run the following query when it inserts the child
insert into CHILD (ID, parent_id) values (default, ?)
thus setting your FK propertly from the very beginning.
Adding updatable=false to the parent entity solved the problem with both an insert and an updated being executed on the child table. However, I have no clue why that's the case and in fact, I don't think what I am doing is correct because it means I cannot update the child table later on if I have to.
I know persisting a new parent with children works for me using em.persists(...).
Using em.merge(...), really I don't know, but it sounds like it should work, but obviously you are running into troubles as your JPA implementation is trying to persists children before parent.
Maybe check if this works for you : https://vnageswararao.wordpress.com/2011/10/06/persist-entities-with-parent-child-relationship-using-jpa/
I don't know if this plays a role in your problem, but keep in mind that em.merge(p); will return a managed entity... and p will remain un-managed, and your children are linked to p.
A) try em.persists(...) rather than em.merge(...)
if you can't
B) you are merging parent... and you cascade is set to CascadeType.PERSIST. Try changing it to
cascade=CascadeType.ALL
or
cascade={CascadeType.PERSIST, CascadeType.MERGE}
I know merge will persists newly created entities and should behave as persists, but these are my best hints.
What you wantto achieve you can achieve with this code.
#Entity
#Table(name = "PARENT")
public class Parent {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private Long parentId;
#OneToMany(cascade = CascadeType.PERSIST)
private List<Child> children;
}
#Entity
#Table(name = "CHILD")
public class Child {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private Long id;
#ManyToOne
Parent 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 have following unidirectional ManyToOne relation:
#Entity
#Table(name = "Child")
public class Child {
#Id
private Integer id;
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Parent parent;
}
#Entity
#Table(name = "parent")
public class Parent{
#Id
private Integer id;
}
When I am trying to delete parent Entity from database I have constraint violation.
ORA-02292: integrity constraint violated - child record found
What I need is that parent Entity is deleted even if it has children, but children Entity should stay.
How do I change this relation?
You can't with JPA if using a relationship. Making it a ManyToOne indicates that a value in the foreign key field will exist in the Parent table. JPA will not be able to distinguish between a null fk value and there being a fk value that just doesn't have an associated row in the Parent table.
If it really must be done (and it shouldn't IMO), you would need to map the Integer foreign key value in Child with a basic mapping instead of the ManyToOne. This allows it to be set independently of there being an existing Parent entity - null means null, a value means a value. You can then query for the associated Parent entity if the entity itself is needed.
Maybe an optional=true parameter on the ManyToOne would help?
#Entity
#Table(name = "Child")
public class Child {
#Id
private Integer id;
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER, optional = true)
private Parent parent;
}
I'm assuming you create the schema straight from Hibernate. The goal is to have the foreign key field nullable in the database.
Actually, it can be done.
#JoinColumn(foreignKey = #ForeignKey(name = "none"))
It's only logical to specify a foreign key as lacking referential integrity constraint when you are describing the table that contains this foreign key column.
Surely it's not a best practice to remove the referential integrity constraints in your presistence layer when you are developing some OLTP system; but for data warehouse-alike solutions (first-load-data-then-check-it-as-a-whole) this may be the correct approach.
I have a problem removing the parent entity from the database. The code looks like this:
public class Parent implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.TABLE)
private Long id;
#Cascade(org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name="parentId")
private Set<Child> children = new HashSet<Child>();
}
public class Child implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.TABLE)
private Long id;
private String name;
}
Query q = em.createQuery("delete from Parent");
q.executeUpdate();
But I get "ERROR: update or delete on table "parent" violates foreign key constraint "fk2f04da924aeb47d8" on table "child"". Is it not possible to cascade the delete of all children? How should you clear the tables otherwise?
The bulk delete operation is not cascaded. From the JPA 1.0 specification:
4.10 Bulk Update and Delete Operations
(...)
A delete operation only applies to
entities of the specified class and
its subclasses. It does not cascade to
related entities.
(...)
So if you want to use a bulk delete, you'll have to do handle relations "manually" (i.e. to delete related entities first).
The other option would be to loop on the parent entities and to call em.remove() (and cascading would work).
Choosing one option or the other will depend on the number of entities to delete and the expected performances.