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.
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.
I am using Spring Boot 2.3.0.
I have a ManyToOne relationship on one side and a OneToMany relationship on the other side. One parent to many children, but many children to one parent. I am trying to be able to delete children without affecting the parent. I have nullable = false on the child side for the parent field because I don't want to end up with accidental nulls for parent in the parent_to_child table. I want things like that to be enforced and get caught.
When I do readerRepository.save(reader) after removing one of the TBRList items (this is the child) from the List<TBRList> in the Reader object (this is the parent), I keep getting an error about the parent field not being able to be null when trying to delete the child. If I set nullable to false on the parent field in the child object, my parent disappears.
I thought I understood how this was supposed to work, but obviously not.
I have:
#Entity //parent
public class Reader implements Serializable {
#Id
#GeneratedValue(strategy = IDENTITY)
#JsonIgnore
private Long id;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "reader", orphanRemoval = true)
Set<TBRList> tbrLists = new HashSet<>();
//other fields, getters, setters, etc.
}
#Entity(name = "tbr") //child
public class TBRList implements Serializable {
#Id
#GeneratedValue(strategy = IDENTITY)
private Long id;
#Column(nullable = false)
private String name;
#JsonIgnore
#ManyToOne
#JoinColumn(name = "reader_id", nullable = false)
private Reader reader;
//other fields, getters, setters, etc
}
In the below snippet, readerRepository.save(reader) is where the org.hibernate.PropertyValueException: not-null property references a null or transient value : com.me.project.entity.TBRList.reader exception is happening.
if (reader.hasTBRList(tbrListName)) {
Iterator<TBRList> it = reader.getTbrLists().iterator();
while (it.hasNext()) {
TBRList tbrList = it.next();
if (tbrList.getName().equals(tbrListName)) {
it.remove();
readerRepository.save(reader);
break;
}
}
}
I tried to also set reader to null in TBRList and delete it via the tbrListRepository, but the same thing happened. In fact, I've tried too many things to remember them all (I try to ask questions as a last result after hours of searching and trying things).
What am I doing wrong with trying to have a Parent/Child relationship, where I don't want Child.parent to be null, and I want to be able to delete a child from the parent without deleting the parent in the process?
I created the same classes and i get this result to execute as you want:
#Entity(name = "tbr")
#Data
public class TBRList {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(nullable = false)
private String name;
#JsonIgnore
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "reader_id", nullable = false)
private Reader reader;
}
#Entity
#Data
public class Reader {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#JsonIgnore
private Long id;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "reader", orphanRemoval = true)
Collection<TBRList> tbrLists ;
}
Here is entities:
#Entity
public class Parent {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
#OneToMany(fetch = FetchType.EAGER, mappedBy = "parent" ,cascade = CascadeType.ALL)
#Fetch(value = FetchMode.SUBSELECT)
private Collection<Child> children;
}
#Entity
public class Child {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
#ManyToOne(cascade = CascadeType.ALL)
private Parent parent;
}
For managing this entities I use spring data (rest). I need to save entities like that:
Parent parent = new Parent();
Child child = new Child()
parent.setChildren(Arrays.asList(child))
springDataRepository.save(parent);
And only Parent object is persisted.
Also as I spot parent and child are created without id. So should I persist only parent, then set it to child and persist child? Can these operation be done with one parent saving action?
See this reply: https://stackoverflow.com/a/7428143
Instead of:
parent.setChildren(Arrays.asList(child))
you should use:
parent.setChildren(new ArrayList<>(Arrays.asList(child)))
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/
I'm struggling with how to create the following relationship in JPA. It's a OneToOne Unidirectional relationship where the parent knows the child, but the child doesn't know the parent, but I want the child table to have the FK to the parent. I've tried different versions of mapped by and join column, but apparently whatever combination I come up with doesn't seem to work. And my googlefoo isn't helping me with an answer either. Here's what I have now.
#Entity
class Parent
{
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "child")
private Child child
}
#Entity
class Child
{
private bool someField
}
Is there a way to do this that I'm just missing?
If this
#OneToOne(..)
#JoinColumn(name = "parent_id")
private Child child;
does not work, try this:
#OneToOne(..)
#JoinColumn(table = "child", name = "parent_id")
private Child child;