JPA unidirectional #ManyToOne relationship remove childs if parent is removed [duplicate] - java

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.

Related

Hibernate - In a one-to-many relationship, a child loses references to the parent when updating

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.

Hibernate many-to-many mapping to the same entity unidirectional

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.

How to map OneToOne recursion entities?

I'm looking for how to solve a recursion and bidiretional onetoone relationship in my Branch object:
#Entity
#Table("BRANCH")
public class Branch {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name")
private String name;
#OneToOne
#JoinColumn(name = "id")
private Branch parent;
#OneToOne(mappedBy = "parent")
#JoinColumn(name = "parent_id")
private Branch child;
//Getters and Setters
}
Is it an accepted pattern?
Right way of bidirectional #OneToOne mapping with self reference:
#Entity
#Table("BRANCH")
public class Branch {
#OneToOne
#JoinColumn(name = "parent_id")
private Branch parent;
#OneToOne(mappedBy = "parent")
private Branch child;
this is ancestor for object in child field
this is descendant for object in parent field
Hierarchy is: parent > this > child
You do not require two foreign keys, single
foreign key in the owning side of the relationship is sufficient. In
JPA the inverse OneToOne must use the mappedBy attribute.
https://en.wikibooks.org/wiki/Java_Persistence/OneToOne#Inverse_Relationships,_Target_Foreign_Keys_and_Mapped_By

Suppress cascade delete annotation function when not needed in JPA CRUD

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);

JPA: fetching bidirectional many to one relationship

I'm a beginner to the JPA/JPQL stuff and I'm having problems fetching many-to-one relationships when I make the relationship bi-directional. Here is the JPQL:
select c from Child c join fetch c.parent
Here are the two simple classes:
#Entity
public class Parent {
#Id
private int id;
private String title;
#OneToMany(mappedBy = "parent")
private Set<Child> children;
}
#Entity
public class Child {
#Id
private int id;
#ManyToOne(fetch = FetchType.LAZY)
private Parent parent;
}
The equivalent SQL query executed by datanucleus is:
SELECT 'com.*.Child' AS NUCLEUS_TYPE,`C`.`ID`,`C`.`PARENT_ID` FROM `CHILD` `C` INNER JOIN `PARENT` `B0` ON `C`.`PARENT_ID` = `B0`.`ID`
Now if I completely remove the reference to "children" in Parent, the SQL is exactly what I need:
SELECT 'com.*.Child' AS NUCLEUS_TYPE,`C`.`ID`,`B0`.`ID`,`B0`.`TITLE` FROM `CHILD` `C` INNER JOIN `PARENT` `B0` ON `C`.`PARENT_ID` = `B0`.`ID`
To be clear: what I'm trying to achieve is to fetch the child's parent with my JPQL query.
Update: I just tried these two classes with EclipseLink and this works, so looks like this problem is Datanucleus-specific.
You need #JoinColumn to create bidirectional relation:
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "parent_id")
private Parent parent;
If you are using JPA annotations select c from Child c and than child.getParent() on objects from database should be enough.
You need to use #JoinColumn in the Child class and try this.
Parent:
#OneToMany(targetEntity = Child.class, fetch = FetchType.EAGER, mappedBy = "parent", cascade=CascadeType.ALL)
Child:
#ManyToOne(targetEntity=Parent.class, fetch = FetchType.EAGER, cascade=CascadeType.ALL)
#JoinColumn(name="id", nullable = false)
And when you fetch the data from parent table it will automatically contains the associated data from the child table in the Set 'children'
Nevermind, this was a Datanucleus bug. It has been fixed in datanucleus-rdbms-3.2.6. This is the commit for the fix:
http://sourceforge.net/p/datanucleus/code/18118/

Categories

Resources