Spring Data JPA - ManyToOne unable to delete child without modifying parent list - spring

I struggle for a week with the following problem:
How is it possible to delete a child entity through a repository without modifying the List on the owning (parent) side of the relation?
Thanks in advance.
I am hoping for some answers!
The child class:
#Entity
#Table(name = "child")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Child implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
private Long id;
#ManyToOne
private Parent parent;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getParent() {
return parent;
}
public void setParent(Parent parent) {
this.parent = parent;
}
}
And the parent class:
#Entity
#Table(name = "parent")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Parent implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
private Long id;
#OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true)
#JsonIgnore
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
private Set<Child> children = new HashSet<>();
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Set<Child> getChildren() {
return children;
}
public void setChildren(Set<Child> children) {
this.children = children;
}
public Parent addChild(Child child) {
this.children.add(child);
child.setParent(this);
return this;
}
public Parent removeChild(Child child) {
this.children.remove(child);
child.setParent(null);
return this;
}
}
And here the test:
#Test
#Transactional
public void testParentToChildRelationShip() {
Parent parent = new Parent();
Child child = new Child();
parent.addChild(child);
parent.addChild(new Child());
parent.addChild(new Child());
parent.addChild(new Child());
parentRepository.save(parent);
Assertions.assertThat(parentRepository.count()).isEqualTo(1L);
Assertions.assertThat(childRepository.count()).isEqualTo(4L);
childRepository.delete(child);
Assertions.assertThat(parentRepository.count()).isEqualTo(1L);
// fails
Assertions.assertThat(childRepository.count()).isEqualTo(3L);
parentRepository.delete(parent.getId());
Assertions.assertThat(parentRepository.count()).isEqualTo(0L);
Assertions.assertThat(childRepository.count()).isEqualTo(0L);
}
The test would work if I insert before deleting the child,
child.getParent().removeChild(child);
but I want to avoid calling this.
Is there a way to make it work with just calling the Child-JPA-Repository.delete method? Or other annotations that I missed?

Since child has association with parent you are facing this issue, you need to remove the link between child and parent either using
parent.removeChild(child);
or
child.getParent().removeChild(child);

Remove these lines from your parent class and also setter and getter of children
#OneToMany(mappedBy = "parent", cascade = CascadeType.ALL, orphanRemoval = true)
#JsonIgnore
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
private Set<Child> children = new HashSet<>();
I think you can remove child mapping from your parent class so you can easily delete the child row using ChildRepository delete() method but problem is that you have to save your child manually using ChildRepository save(). You can not save child object with parent object using ParentRepository. Change your Test code like below for saving child and parent
Parent parent = new Parent();
Parent parent = parentRepository.save(parent);
Child child = new Child();
child.setParent(parent);
childRepository.save(child);

Related

Can orphanRemoval be used for depth > 1?

Should all child1 and child2 (depth 2) be deleted, when a parent gets deleted?
Database is Informix, constraints are created in the child tables. Deletion of parent is performed with JpaRepository.deleteById(parent.getId()), both do nothing and no error message occurs (show_sql just lists selects). Spring version is 5.3.19, spring-data-jpa 2.6.4.
Current example code:
#Entity
#Table(name = "parent_table")
public class Parent
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToMany(orphanRemoval = true, cascade = CascadeType.ALL, mappedBy = "parent", fetch = FetchType.EAGER)
private Set<Child1> children = new HashSet<>();
}
#Entity
#Table(name = "child1_table")
public class Child1
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne
#JoinColumn(name = "parentid", referencedColumnName = "id", nullable = false)
private Parent parent;
#OneToMany(orphanRemoval = true, cascade = CascadeType.ALL, mappedBy = "child1", fetch = FetchType.EAGER)
private Set<Child2> children = new HashSet<>();
}
#Entity
#Table(name = "child2_table")
public class Child2
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne
#JoinColumn(name = "child1id", referencedColumnName = "id", nullable = false)
private Child1 child1;
}
Update
added
#PreRemove
private void deleteChildren()
{
children.clear();
}
to Parent and Child1. Now children get deleted, but not the Parent.
In fact, Parent also had a parent and I had to remove this from it's Set too.
So the solution is:
Clear children Sets
#PreRemove
private void preRemove()
{
children.clear();
}
Remove the root entity from its parent in case it has a parent
#PreRemove
private void preRemove()
{
children.clear();
parentsParent.getParents().remove(this);
}

How to update an entity using #onetomany relationship in JPA

I created an entity using a tree structure and parent child relations using #ManyToOne and #OneToMany annotations. However only changes I made to a parent of an entity are processed in the database.
My entity class looks like this:
#Entity
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class OKR {
#Id
#Column(name = "ID")
#GeneratedValue
private UUID id;
#NotBlank
private String name;
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name = "parentid", referencedColumnName = "id")
private OKR parent;
#OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
private List<OKR> children;
private boolean isRoot;
public OKR(String name, OKR parent, List<OKR> children){
this.name = name;
this.children = children;
if (this.children==null){
this.children = new ArrayList<>();
}
this.parent = parent;
if (parent == null||parent.equals(new UUID(0,0))){
isRoot = true;
}else{
isRoot = false;
}
}
protected OKR(){
isRoot = true;
children = new ArrayList<>();
}
When I update an OKR by changing its parent, the parent OKR is updated as well. However when I update an OKR by only adding a child, the child OKR does not get updated. I'm fairly new to JPA and I also noticed that there is no table for children inside my database. So my question is what is the easiest way to update all relations when only updating the children of an entity?

How can I save ordered collection in JPA/Hibernate?

I have an main entity:
#Data
#Entity
#Table(name = "MAINS")
public class Main {
...
#OneToMany(mappedBy = "main", cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = LAZY)
private List<Chield> children;
And I have a child entity:
#Data
#Entity
#Table(name = "CHILDS")
public class Child {
#Id
#Column(name = "GUID")
private String guid;
#Column(name = "NAME")
private String name;
#Column(name = "Age")
private Integer age;
And I try to save one Main with many Child:
List<Child> children = new ArrayList<>();
for (int i = 0, i< 10; i++) {
Child child = new Child();
child.setId(i);
child.setName("Name"+i);
child.setAge(10+i);
children.add(child);
}
main.setChildren(children);
MainRepository.save(main);
But I need to save these children ordered. I can add a new column to Child - order and set child.setOrder(order++); and when I select this list I can sort it by order field.
But can I do it differently? Without adding of the new column?
If I get your question correctly it could be helpful to check such options:
OrderColumn
#OrderColumn(name = "index_id")
private List<Child> changes = new ArrayList<>();
OR
#OrderBy("id")
private Set<Child>list = new LinkedHashSet<>();
OR
#SortNatural
private SortedSet<Child> children = new TreeSet<Child>();
DOCS: naturalSort
OR for custom comparator: #SortComparator(SortById.class)
DOCS: customSort
public class SortById implements Comparator<Child> {
Logger log = Logger.getLogger(SortById.class.getSimpleName());
#Override
public int compare(Child o1, Child o2) {
log.info("Child.compare");
return o1.getId().compareTo(o2.getId());
}
}

can i retrieve child entity containing parent entity id by using Spring Data Jpa one-to-many unidirectional relationship

Here is my parent entity:
#Entity
public class Parent {
#Id
int parentId;
String name;
#OneToMany()
#JoinColumn(name="parent_id")
List<Child> childList;
}
public class Child {
int childId;
//if i am taking this property as non-transient application won't run. but i need parent Id without changing the class structure..
#Transient
int parentId;
// ... some other properties
}
Insertion is successful as two tables are created : parent(id,name),
child(id,name,parent_id).
But when I retrieve the Parent record then in the Child object, the
parentId property remains 0.
i found a way to retrieve the parentid from the child entity by doing a bidirectional mapping of the Parent-child relationship. You can get the ParentId by using a getter method from the child entity that returns the parentId.
Parent Entity
#Entity
#Table(name = "Parent")
public class Parent implements Serializable {
#Column(name = "ID", nullable = false, length = 10)
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int Id;
#Column(name = "Name")
public String name;
#OneToMany(mappedBy = "parent", fetch = FetchType.LAZY)
private Set<Child> children = new HashSet<>();
public Parent() {
}
//getters and setters omitted for brevity
}
//Child Entity
#Entity
#Table(name = "Child")
public class Child implements Serializable{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int Id;
#Column(name = "Name")
private String name;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "parentId", nullable = false)
#OnDelete(action = OnDeleteAction.CASCADE)
private Parent parent;
public Child() {
}
public int getId() {
return Id;
}
public void setId(int id) {
Id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
#JsonIgnore
public Parent getParent() {
return parent;
}
//getter method to retrieve the parent id in the child entity
public int getParent_Id(){
return parent.getId();
}
public void setParent(Parent parent) {
this.parent = parent;
}
}
Notice the method getParent_Id() which returns the parent Id, since getter methods are used to return object, the parentId would be returned as part of the child entity anytime it is fetched.
Also note the use of #JsonIgnore on the getParent() method, this to avoid an infinite recursion going on during serialization since Parent refers to Child and Child refer to Parent.

MIssing parent reference in a bidirectional hibernate mapping

I have a spring rest backend with two entities with a bidirectional relationshop (one-to-many, many to one). To overcome nested fetching issues, #JsonManagedReference/#JsonBackReference has been used for a perent/child relationship between entities.
The entites look as this:
#Entity
#Table(name = "Parent")
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Parent implements java.io.Serializable {
private Integer id;
private List<Child> childList;
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "ID", unique = true, nullable = false)
public Integer getId() {
return this.id;
}
public void setId(Integer id) {
this.id = id;
}
#OneToMany(mappedBy = "parent", fetch = FetchType.LAZY)
#JsonManagedReference
public List<Child> getChildList() {
return childList;
}
public void setChildListe(List<Child> childListe) {
this.childList = childList;
}
}
#Entity
#Table(name = "Child")
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Child implements java.io.Serializable {
private Integer id;
private Parent parent;
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "ID", unique = true, nullable = false)
public Integer getId() {
return this.id;
}
public void setId(Integer id) {
this.id = id;
}
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "ParentID")
#JsonBackReference
public Parent getParent() {
return parent;
}
public void setParent(Parent parent) {
this.parent = parent;
}
}
This works fine when fetching the Parent element, the childset is then fetched alongside and displayed as an json-array.
However, there is no reference to parent in the child element due to the usage of jsonbackreferance.
How can solve this issue ? I need parent reference when fetching child
That would lead to an infinite loop when serializing to JSON. That's the whole reason we don't do bi-direction JSON relationships.
What I would do is add an additional column to the child entity if you need the ID alone.
private Integer parentId;
#Column(name = "ParentID", insertable=false, updateable=false)
public Integer getParentId() {
return parentId;
}

Resources