I have two entities Parent and Child that are in the bidirectional relationship like this:
#Entity
public class Parent implements Serializable {
#Id
#GeneratedValue
private long id;
#OneToMany(mappedBy = "parent", cascade = CascadeType.REMOVE)
private Set<Child> children;
}
#Entity
public class Child implements Serializable {
#Id
#GeneratedValue
private long id;
#ManyToOne
#JoinColumn
private Parent parent;
}
Through annotation, when I delete an entry of Parent in the database, any child related to it will also be deleted through cascade.
However, there are cases where a Parent has no child; therefore, a cascade delete is not needed.
I want to differentiate my delete operation in terms of whether cascade is needed (e.g. was the deletion a simple delete or a delete that requires cascade). Without making an explicit call on Parent to check if children are null, is there a way for JPA to automatically suppress the cascade annotation when not needed in a command like this one below?
this.parentRepository.delete(parentID);
Related
Say I have a unidirectional #ManyToOne relationship like the following:
#Entity
public class Parent implements Serializable {
#Id
#GeneratedValue
private long id;
}
#Entity
public class Child implements Serializable {
#Id
#GeneratedValue
private long id;
#ManyToOne
#JoinColumn
private Parent parent;
}
If I have a parent P and children C1...Cn referencing back to P, is there a clean and pretty way in JPA to automatically remove the children C1...Cn when P is removed (i.e. entityManager.remove(P))?
What I'm looking for is a functionality similar to ON DELETE CASCADE in SQL.
If you are using hibernate as your JPA provider you can use the annotation #OnDelete. This annotation will add to the relation the trigger ON DELETE CASCADE, which delegates the deletion of the children to the database.
Example:
public class Parent {
#Id
private long id;
}
public class Child {
#Id
private long id;
#ManyToOne
#OnDelete(action = OnDeleteAction.CASCADE)
private Parent parent;
}
With this solution a unidirectional relationship from the child to the parent is enough to automatically remove all children. This solution does not need any listeners etc. Also a JPQL query like DELETE FROM Parent WHERE id = 1 will remove the children.
Relationships in JPA are always unidirectional, unless you associate the parent with the child in both directions. Cascading REMOVE operations from the parent to the child will require a relation from the parent to the child (not just the opposite).
You'll therefore need to do this:
Either, change the unidirectional #ManyToOne relationship to a bi-directional #ManyToOne, or a unidirectional #OneToMany. You can then cascade REMOVE operations so that EntityManager.remove will remove the parent and the children. You can also specify orphanRemoval as true, to delete any orphaned children when the child entity in the parent collection is set to null, i.e. remove the child when it is not present in any parent's collection.
Or, specify the foreign key constraint in the child table as ON DELETE CASCADE. You'll need to invoke EntityManager.clear() after calling EntityManager.remove(parent) as the persistence context needs to be refreshed - the child entities are not supposed to exist in the persistence context after they've been deleted in the database.
Create a bi-directional relationship, like this:
#Entity
public class Parent implements Serializable {
#Id
#GeneratedValue
private long id;
#OneToMany(mappedBy = "parent", cascade = CascadeType.REMOVE)
private Set<Child> children;
}
I have seen in unidirectional #ManytoOne, delete don't work as expected.
When parent is deleted, ideally child should also be deleted, but only parent is deleted and child is NOT deleted and is left as orphan
Technology used are Spring Boot/Spring Data JPA/Hibernate
Sprint Boot : 2.1.2.RELEASE
Spring Data JPA/Hibernate is used to delete row .eg
parentRepository.delete(parent)
ParentRepository extends standard CRUD repository as shown below
ParentRepository extends CrudRepository<T, ID>
Following are my entity class
#Entity(name = “child”)
public class Child {
#Id
#GeneratedValue
private long id;
#ManyToOne( fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = “parent_id", nullable = false)
#OnDelete(action = OnDeleteAction.CASCADE)
private Parent parent;
}
#Entity(name = “parent”)
public class Parent {
#Id
#GeneratedValue
private long id;
#Column(nullable = false, length = 50)
private String firstName;
}
Use this way to delete only one side
#ManyToOne(cascade=CascadeType.PERSIST, fetch = FetchType.LAZY)
// #JoinColumn(name = "qid")
#JoinColumn(name = "qid", referencedColumnName = "qid", foreignKey = #ForeignKey(name = "qid"), nullable = false)
// #JsonIgnore
#JsonBackReference
private QueueGroup queueGroup;
#Cascade(org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
Given annotation worked for me. Can have a try
For Example :-
public class Parent{
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
#Column(name="cct_id")
private Integer cct_id;
#OneToMany(cascade=CascadeType.REMOVE, fetch=FetchType.EAGER,mappedBy="clinicalCareTeam", orphanRemoval=true)
#Cascade(org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
private List<Child> childs;
}
public class Child{
#ManyToOne(fetch=FetchType.EAGER)
#JoinColumn(name="cct_id")
private Parent parent;
}
You don't need to use bi-directional association instead of your code, you have just to add CascaType.Remove as a property to ManyToOne annotation, then use #OnDelete(action = OnDeleteAction.CASCADE), it's works fine for me.
As in the title, when performing the update operation, the previous child loses the reference to the parent.
Parent side
#OneToMany(cascade =CascadeType.ALL)
#JoinColumn(name = "individual_id")
private List<ContactMedium> contactMedium;
Children side
#Entity
#Table(name = "contactMedium")
#Data
#AllArgsConstructor
#NoArgsConstructor
public class ContactMedium
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id ;
#ManyToOne
private Individual individual;
Patch operation
public Individual patch(Individual individual, Long id) {
Individual objectToSave = individual;
objectToSave.setId(id);
return individualRepository.save(objectToSave);
}
When updating, the previous property loses references to the child. How can I prevent this?
Your mappings seems wrong. Ideally they should be as below:
#Entity
#Table(name = "contactMedium")
#Data
#AllArgsConstructor
#NoArgsConstructor
public class ContactMedium
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id ;
#ManyToOne
#JoinColumn
private Individual individual;
and
#OneToMany(mappedBy = "individual", cascade = CascadeType.ALL)
private List<ContactMedium> contactMedium;
You need to save the ContactMedium and Individual will automatically be saved. Here ContactMedium has the foreign key reference to Individual (and that is what is depicted in your database table screenshot).
Often one use mappedBy as parameter to #OneToMany instead of #JoinColumn to make the relationship two-ways.
Can you please try to change
#OneToMany(cascade =CascadeType.ALL)
#JoinColumn(name = "individual_id")
private List<ContactMedium> contactMedium;
to
#OneToMany(mappedBy = "individual", cascade =CascadeType.ALL)
private List<ContactMedium> contactMedium;
and see if that worked better?
I think you must add the #OneToMany(mappedBy="individual" , cascade =CascadeType.PERSIST) and the #JoinColumn in the #ManyToOne as below:
#OneToMany(mappedBy = "individual", cascade =CascadeType.PERSIST)
private List<ContactMedium> contactMedium;
#ManyToOne
#JoinColumn(name = "individual_id")
private Individual individual;
You should retrieve the entity from the database using the ID first and then update the specific fields and persist the updated entity back.
I need to delete my child entity if one of the parent are being removed.
Right now child entity will be removed if both parents are removed and I need to change the behaviour.
For example if you have:
class Parent1 {
#Id
Long id;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
Set<Child> children = new HashSet();
}
class Parent2 {
#Id
Long id;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
Set<Child> children = new HashSet();
}
class Child {
#Id
Long id;
#ManyToOne
Parent1 p1;
#ManyToOne(fetch = FetchType.EAGER)
Parent2 p2;
}
I tried to use some combinations with orphanRemoval, but it didn't work. Do you have any other suggestions?
I'd put this kind of logic simply in a method that removes a parent. It would then also remove the other parent. This method could live on the Child class or a separate service.
Note, that currently your mapping is missing mappedBy values for the bidirectional relationships.
I have the table that represents kind of Tree node. The mapping below illustrates many-to-many mapping of node.
#Entity
public class Node {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToMany
#JoinTable(name = "node_dependency",
joinColumns = {#JoinColumn(name = "parent_id")},
inverseJoinColumns = {#JoinColumn(name = "child_id")})
private List<Node> childNodes = new ArrayList<>();
}
It works but I would like to have separate table mapping for the delete query simplicity.
#Entity
public class NodeRelation {
#ManyToOne
private Node parent;
#ManyToOne
private Node child;
}
If I have NodeRelation I can easily find nodes that re-used on different layers of the tree and cannot be safely deletes which is more difficult to do having instead of (One-to-many on Node + Many-to-One on FK in NodeRelation) only Many-to-Many mapping.
I tried different combinations of mapping with composite key that represented by NodeRelation but there is no luck (validation according db schema didn't passed). Please, advice me which mapping is better in this use case.
It is better to not use childNodes association in the Node.
It will be convenient to add id to the NodeRelation.
#Entity
public class Node {
#Id
#GeneratedValue
private Long id;
}
#Entity
public class NodeRelation {
#Id
#GeneratedValue
private Long id;
#ManyToOne
private Node parent;
#ManyToOne
private Node child;
}
Also you can add an unique constraint (parent, child) to NodeRelation (to have the same behavior as #ManyToMany join table has).
It will need to do queries on NodeRelation table only.
I have two entities Business which is composed of a list of Departments
#Entity
#Table(name = "Business")
public class Business implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "Id")
private Long id;
#OneToMany(mappedBy = "business",
cascade = {CascadeType.PERSIST, CascadeType.REMOVE})
private List<Department> departments;
#OneToMany(mappedBy = "business", orphanRemoval = true,
cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE})
private List<Process> processs;
#ManyToMany
private List<Competence> competences;
}
#Entity
#Table(name = "Department")
public class Department implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(mappedBy = "father",
cascade = {CascadeType.PERSIST, CascadeType.REMOVE})
private List<Department> departments;
}
When I try to remove a business instance I get a Mysql Exception
Cannot delete or update a parent row: a foreign key constraint fails (evac_java.Department, CONSTRAINT FK_Department_Business FOREIGN KEY (Business) REFERENCES Business (Id)):HY000 - null
Which means I can't delete the business instance because it has departments associated with it, but a department cannot exists by itself so I want to delete all business's departments when it gets removed. I thought I would achieve this by adding cascade = CascadeType.REMOVE to the #OneToMany annotation in the business entity, but it does not work.
I did a search on the net and I found a lot of questions similar to this one on stackoverflow but they all suggest the same: add cascade = CascadeType.REMOVE or CascadeType.ALL
So I'm wondering if I'm missing somethig.
I'm using Glassfish 4.1 and EclipseLink
I tried with
#OneToMany(mappedBy = "business", orphanRemoval = true)
private List<Department> departments;
on the business entity but it does not work either
Here's the method I'm using to remove entities which is declared in an abstract class
public void remove(T entity) {
getEntityManager().remove(getEntityManager().merge(entity));
}
JPA can only remove and cascade the remove over entities it knows about, and if you have not been maintaining both sides of this bidirectional relationship, issues like this will arise. If the collection of departments is empty, try an em.refresh() before the remove, forcing JPA to populate all relationships so that they can be correctly removed, though it is better to maintain both sides of the relationship as changes are made to avoid the database hit.