Hibernate: cascade deletion of a child collection - java

Suppose I have 2 Java objects: Parent and Child.
The relationship between them is Child -> Parent = many-to-one, i.e. a number of Child objects can be associated with the same Parent.
A Child object is holding a reference to its Parent meanwhile Parent object has no field to address its Children.
In Hibernate that results in having a many-to-one element in Child mapping; Parent's mapping doesn't contain one-to-many entry since there is no need for a Parent object to have a collection field to reference all its Child objects.
Now, when Parent is deleted Oracle throws an exception that the entity cannot be deleted while there are child entities referencing it.
With this object model, is there a way to casacadely delete all Child objects that belong to Parent object when the latter gets deleted?

1> make bi directional mapping to Child <--> parent. mention cascade delete.
2> you know who are the childs object of this parent, first delete all childs of this parent and then try to delete parent.
EDIT:
check this example link
Hibernate Bidirectional Example

Class Parent
import java.util.Set;
import javax.persistence.*;
#Entity
#Table(name = "Parent")
public class Parent{
#Id
#GeneratedValue
private Integer id;
private String name;
#OneToMany(mappedBy="Child", cascade=CascadeType.ALL)
private Set<Child> child;
}
consider second class called child
import javax.persistence.*;
#Entity
#Table(name = "Child")
public class Child{
#Id
#GeneratedValue
private Integer id;
private String lastname;
#ManyToOne
#JoinColumn(name = "id")
private Parent parent;
}
just make changes in your POJO's accordingly it will not throw exception

Related

Spring-Data-Jpa Cascading from child to parent

Let say I have an app to handle a collection of books.
My app allow to add a new book to the library. When creating the book, user can select the Author in the list, and if the author doesn't exist yet, he's able to add him to the list, providing his name to a form field.
When the form is filled, data are sent to a WS, something like
{
"name" : "The Book name"
"author" : {
"name" : "author's name"
}
}
Then I map json into my entity which would be
Book :
#Entity
#Table(name = "book")
public class Book{
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name")
private String name;
#ManyToOne(fetch = FetchType.LAZY)
private Author author;
}
Author
#Entity
#Table(name = "author")
public class Author{
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name")
private String name;
#OneToMany(mappedBy = "author", cascade = { CascadeType.ALL })
private List<Book> books;
}
This will not work as if user tries to add a new author, when I'll try to .save() I'll get an error :
org.hibernate.TransientPropertyValueException: object references an
unsaved transient instance
Is there a way to handle the case with Spring-Data-Jpa, or do I have to check manually that I got an author id in the json, and if not - meaning that this is a new author - mannually run the author creation and then save the new book?
Thx!
As you're guessing, and as the Javadoc says, cascade operations that must be cascaded to the target of the association". However, be sure you understand that the mappedBy defines the owning entity of the relationship. The owning entity is the entity that actually does the persisting operations, unless overridden by a cascade setting. In this case Child is the owning entity.
#Entity
public class Parent {
#Id #GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#OneToMany(mappedBy="parent")
private Set<Child> children;
The cascade setting on the Parent works when you create a Set of children and set it into the Parent and then save the Parent. Then the save operation will cascade from the Parent to the children. This is a more typical and the expected use case of a cascade setting. However, it does cause database operations to happen auto-magically and this is not always a good thing.
A Cascade setting on the child will happen when the child is persisted, so you could put a cascade annotation there, but read on ...
#Entity
public class Child {
#Id #GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#ManyToOne(cascade=CascadeType.ALL)
private Parent parent;
You will persist both the parent and the child by persisting the child.
tx.begin();
Parent p = new Parent();
Child c = new Child();
c.setParent(p);
em.persist(c);
tx.commit();
and when you delete the child it will delete both the parent and the child.
tx.begin();
Child cFound = em.find(Child.class, 1L);
em.remove(cFound);
tx.commit();
em.clear();
this is where you have problems. What happens if you have more than one child?
em.clear();
tx.begin();
p = new Parent();
Child c1 = new Child();
Child c2 = new Child();
c1.setParent(p);
c2.setParent(p);
em.persist(c1);
em.persist(c2);
tx.commit();
All well and nice until you delete one of the children
em.clear();
tx.begin();
cFound = em.find(Child.class, 2L);
em.remove(cFound);
tx.commit();
then you will get an integrity constraint violation when the cascade propagates to the Parent but there is still a second Child in the database. Sure you could cure it by deleting all the children in a single commit but that's getting kind of messy isn't it?
Conceptually people tend to think that propagation goes from Parent to Child and so it is very counterintuitive to have it otherwise. Further, what about a situation where you don't want to delete the author just because the store sold all his or her books? In this case you might be mixing cascade, sometimes from child to parent and in other cases from parent to child.
Generally I think it is better to be very precise in your database code. It's much easier to read, understand, and maintain code that specifically saves the parent first then the child or children than to have an annotation somewhere else that I may or may not be aware of that is doing additional database operations implicitly.

How to save parent entity without inserting child in one to one relationship by shared primary key in hibernate?

I am using one to one relation by shared primary key in hibernate,
whenever I call the save method to insert parent entity without inserying child entity I get below
exception
org.hibernate.id.IdentifierGenerationException: attempted to assign id from null one-to-one property [com.example.sms.domain.Child.parent]
Code of child class entity with mapping of parent class entity is
given below
#Entity
#Table(name = "t_child")
public class Child {
#Id
#Column(name="user_id", unique=true, nullable=false)
#GeneratedValue(generator="gen")
#GenericGenerator(name="gen", strategy="foreign", parameters=#Parameter(name="property", value="user"))
private Long id;
#OneToOne(optional = false,fetch = FetchType.LAZY)
#PrimaryKeyJoinColumn
private Parent parent;
And in parent class entity i have mapped child class entity like below
#OneToOne(mappedBy="parent", cascade=CascadeType.ALL)
private Child child;
Is there any way to save only parent entity without inserting child entity?
The entity relationship diagram is not showing us a one-to-one relationship. But let us assume it is.
If I understood you well the child entity is dependent on the parent entity and it does not have its own identity. So you don't need to define an ID attribute for it; so the mapping for the Child entity should look like the following:
#Entity
#Table(name = "t_child")
public class Child {
#Id
#OneToOne(optional = false,fetch = FetchType.LAZY)
#JoinColumn(name="parent_id")
private Parent parent;
// ...
}
and the Parententity should look like the following:
#Entity
#Table(name = "t_parent")
public class Parent {
#Id
#GeneratedValue(generator="gen")
private Long id;
#OneToOne(mappedBy="parent")
private Child child;
// ...
}
That should be the mapping. Now, if you want to save parent without child do as follows:
Parent parent = new Parent();
// set attribute values
entityManager.persist(parent);
But if you want to save child, you should know that you first must have to save parent because the child is dependent on the parent, which means the parent must exist.
Finally after 1 day I have found the solution myself
This code Was correct. Mapstruct was creating a problem while
converting Dto to domain object
#Mappings({
#Mapping(source = "fatherName", target = "child.childDetail.fatherName"),
#Mapping(source = "motherName", target = "child.childDetail.motherName"),
#Mapping(source = "firstName", target = "child.childDetail.firstName"),
#Mapping(source = "lastName", target = "child.childDetail.lastName"),
#Mapping(source = "dateOfBirth", target = "child.childDetail.dateOfBirth"),
})
public User ParentDtoToParent(ParentDto parentDto);
I was using the Same DTO object that I was using while storing every
child detail But to store only parent entity I was not sending any
child details value in the Json Format. So mapstruct Automatically
assigned NULL value to the Child attribute(firstname,lastname etc)
When Hibernate Was saving the object it got object With the Null value
of Attributes instead of NULL Object

Hibernate annotations work better the "wrong" way around?

I've been looking around the net for a decent answer to this but all I've gotten is confused. I'm struggling with how #ManyToOne annotations in hibernate are supposed to work - because #OneToMany with #JoinColumn seems far superior because hibernate inserts the foreign keys properly instead of me having to assign the child object in java before saving.
Basically as the most simple example I have something like this:
Parent Table: id int,
Child Table : id int, parentFk int
public class Parent{
#Id
#GeneratedValue
private Integer id;
#OneToMany
#JoinColumn(name = "parentFk")
private List<Child> children;
//setters and getters
}
public class Child{
#Id
#GeneratedValue
private Integer id;
#Column
private Integer parentFk;
//setters and getters
}
Now if I send some json that maps to a Parent class and ask hibernate to save it, it will save everything in the table including the new id of the parent in the parentFk field. However I've been lead to believe that this is actually the wrong way of doing things and that I should be doing #OneToMany(mappedBy = "id") in the Parent class instead - and then also having #ManyToOne with #JoinColumn and a Parent object in the Child class.
The problem is doing it this way I have to manually set the parent object in the child via java code before hibernate will save the id of the parent in the parentFk field correctly...it just seems like a very long winded of doing something that is already working perfectly for me (albeit I cannot access the parent object from the child).
Moreover I've tried to remove the parentFk field in the child object and use #ManyToOne with a parent object reference, but hibernate doesn't seem to like it. Am I doing this all wrong?
Your mapping should be something like this.
public class Parent{
#Id
#GeneratedValue
private Integer id;
#OneToMany(mappedBy="parent", cascade={CascadeType.ALL})
private List<Child> children;
//setters and getters
}
public class Child{
#Id
#GeneratedValue
private Integer id;
#ManyToOne
#JoinColumn(name="parentFk")
private Parent parent;
//setters and getters
}
You should also have an add(Child child) method on your parent (and remove) to manage the relation ship. And assuming Parent and Child are in the same package I would make the setParent on the Child package protected, i.e no access modifier (setParent(Parent parent) { this.parent=parent}).
public void add(Child child) {
child.setParent(this);
this.children.add(child);
}
When doing it like this hibernate will be able to execute the right queries.
Fixed by using #JsonManagedReference on the #OneToMany mappings and #JsonBackReference on the #ManyToOne

How to save bidirectional association without cascading using Spring data JPA?

Lets say I have bidirectional one-to-many association between Parent-Child, mapped as follows:
Parent.java:
#Entity
public class Parent {
#Id
private Integer id;
#OneToMany(mappedBy = "parent")
private List<Child> childs = new ArrayList<>();
...
and Child.java:
#Entity
public class Child {
#Id
private Integer id;
#ManyToOne
#JoinColumn(name = "parent_id")
private Parent parent;
...
When I run this code
Parent parent = new Parent(1);
Child child = new Child(1);
Child child2 = new Child(2);
child.setParent(parent);
child2.setParent(parent);
parent.getChilds().add(child);
parent.getChilds().add(child2);
parentRepository.save(parent);
I get exception
Unable to find Child with id 1
Saving a child first doesn't help either, only exception is different
well i am sorry for posting a not sure answer but i cannot post a comment cause of reputation.
i think you have a cross reference problem, because simply by referencing the parent from the child you can get the childs that parent has with a simple query. instead you cross reference the child association resulting to many object problems. if you want i can post you a class diagram for better explanation. hope it helps
Try
#OneToMany(mappedBy = "parent", cascade={CascadeType.PERSIST})
private List<Child> childs = new ArrayList<>();
(see also JPA #ManyToOne with CascadeType.ALL for example)

EJB3 and manual hierarchy persistence

I have a legacy database, which I am using EJB3 to model. The database is in quite a poor shape, and we have certain unusual restrictions on how we insert into the DB. Now I want to model the database in a hierarchy that fits in with the DB strucuture, but I want to be able to manually insert each entity individually without the persistence manager trying to persist the entities children.
I am trying something like the following (boilerplate left out):
#Entity
#Table(name = "PARENT_TABLE")
public class Parent {
#Id
#Column(name = "ID")
int id;
#OneToMany
List<Child> children;
}
#Entity
#Table(name = "CHILD_TABLE")
public class Child {
#Id
#Column(name = "ID")
int id;
}
Now this throws an exception:
java.lang.IllegalStateException: During synchronization a new object was found through a relationship that was not marked cascade PERSIST
Now I know the entity isn't marked PERSIST - I don't want the EntityManager to persist it! I want to be able to persist the parent first, and then the child - but not together. There are good reasons for wanting to do it this way, but it doesn't seem to want to play.
Heh welcome to the hair-pulling that is JPA configuration.
In your case you have two choices:
Manually persist the new object; or
Automatically persist it.
To automatically persist it you need to annotate the relationship. This is a common one-to-many idiom:
#Entity
#Table(name = "PARENT_TABLE")
public class Parent {
#Id private Long id;
#OneToMany(mappedBy = "parent", cascade = CascadeType.PERSIST)
private Collection<Child> children;
public void addChild(Child child) {
if (children == null) {
children = new ArrayList<Child>();
}
child.setParent(parent);
children.add(child);
}
}
#Entity
#Table(name = "CHILD_TABLE")
public class Child {
#Id private Long id;
#ManyToOne
private Parent parent;
public void setParent(Parnet parent) {
this.parent = parent;
}
}
Parent parent = // build or load parent
Child child = // build child
parent.addChild(child);
Because of the cascade persist this will work.
Note: You have to manage the relationship at a Java level yourself, hence manually setting the parent. This is important.
Without it you need to manually persist the object. You'll need an EntityManager to do that, in which case it is as simple as:
entityManager.persist(child);
At which point it will work correctly (assuming everything else does).
For purely child entities I would favour the annotation approach. It's just easier.
There is one gotcha I'll mention with JPA:
Parent parent = new Parent();
entityManager.persist(parent);
Child child = new Child();
parent.addChild(child);
Now I'm a little rusty on this but I believe that you may run into problems if you do this because the parent was persisted before the child was added. Be careful and check this case no matter what you do.

Categories