How does JPA orphanRemoval=true differ from the ON DELETE CASCADE DML clause

HibernateJpaCascadeCascading DeletesOrphan Removal

Hibernate Problem Overview


I am a little confused about the JPA 2.0 orphanRemoval attribute.

I think I can see it is needed when I use my JPA provider's DB generation tools to create the underlying database DDL to have an ON DELETE CASCADE on the particular relation.

However, if the DB exists and it already has an ON DELETE CASCADE on the relation, is this not enough to cascade the deletion appropriately? What does the orphanRemoval do in addition?

Cheers

Hibernate Solutions


Solution 1 - Hibernate

orphanRemoval has nothing to do with ON DELETE CASCADE.

orphanRemoval is an entirely ORM-specific thing. It marks "child" entity to be removed when it's no longer referenced from the "parent" entity, e.g. when you remove the child entity from the corresponding collection of the parent entity.

ON DELETE CASCADE is a database-specific thing, it deletes the "child" row in the database when the "parent" row is deleted.

Solution 2 - Hibernate

An example taken form here:

When an Employee entity object is removed, the remove operation is cascaded to the referenced Address entity object. In this regard, orphanRemoval=true and cascade=CascadeType.REMOVE are identical, and if orphanRemoval=true is specified, CascadeType.REMOVE is redundant.

The difference between the two settings is in the response to disconnecting a relationship. For example, such as when setting the address field to null or to another Address object.

  • If orphanRemoval=true is specified the disconnected Address instance is automatically removed. This is useful for cleaning up dependent objects (e.g. Address) that should not exist without a reference from an owner object (e.g. Employee).

  • If only cascade=CascadeType.REMOVE is specified, no automatic action is taken since disconnecting a relationship is not a remove operation.

To avoid dangling references as a result of orphan removal, this feature should only be enabled for fields that hold private non shared dependent objects.

I hope this makes it more clear.

Solution 3 - Hibernate

The moment you remove a child entity from the collection you will also be removing that child entity from the DB as well. orphanRemoval also implies that you cannot change parents; if there's a department that has employees, once you remove that employee to put it in another deparment, you will have inadvertantly removed that employee from the DB at flush/commit(whichver comes first). The morale is to set orphanRemoval to true so long as you are certain that children of that parent will not migrate to a different parent throughout their existence. Turning on orphanRemoval also automatically adds REMOVE to cascade list.

Solution 4 - Hibernate

Entity state transitions

JPA translates entity state transitions to SQL statements, like INSERT, UPDATE or DELETE.

JPA entity state transitions

When you persist an entity, you are scheduling the INSERT statement to be executed when the EntityManager is flushed, either automatically or manually.

when you remove an entity, you are scheduling the DELETE statement, which will be executed when the Persistence Context is flushed.

Cascading entity state transitions

For convenience, JPA allows you to propagate entity state transitions from parent entities to child one.

So, if you have a parent Post entity that has a @OneToMany association with the PostComment child entity:

Post and PostComment entities

The comments collection in the Post entity is mapped as follows:

@OneToMany(
    mappedBy = "post", 
    cascade = CascadeType.ALL,
    orphanRemoval = true
)
private List<Comment> comments = new ArrayList<>();

CascadeType.ALL

The cascade attribute tells the JPA provider to pass the entity state transition from the parent Post entity to all PostComment entities contained in the comments collection.

So, if you remove the Post entity:

Post post = entityManager.find(Post.class, 1L);
assertEquals(2, post.getComments().size());

entityManager.remove(post);

The JPA provider is going to remove the PostComment entity first, and when all child entities are deleted, it will delete the Post entity as well:

DELETE FROM post_comment WHERE id = 1
DELETE FROM post_comment WHERE id = 2

DELETE FROM post WHERE id = 1

Orphan removal

When you set the orphanRemoval attribute to true, the JPA provider is going to schedule a remove operation when the child entity is removed from the collection.

So, in our case,

Post post = entityManager.find(Post.class, 1L);
assertEquals(2, post.getComments().size());

PostComment postComment = post.getComments().get(0);
assertEquals(1L, postComment.getId());

post.getComments().remove(postComment);

The JPA provider is going to remove the associated post_comment record since the PostComment entity is no longer referenced in the comments collection:

DELETE FROM post_comment WHERE id = 1

ON DELETE CASCADE

The ON DELETE CASCADE is defined at the FK level:

ALTER TABLE post_comment 
ADD CONSTRAINT fk_post_comment_post_id 
FOREIGN KEY (post_id) REFERENCES post 
ON DELETE CASCADE;

Once you do that, if you delete a post row:

DELETE FROM post WHERE id = 1

All the associated post_comment entities are removed automatically by the database engine. However, this can be a very dangerous operation if you delete a root entity by mistake.

Conclusion

The advantage of the JPA cascade and orphanRemoval options is that you can also benefit from optimistic locking to prevent lost updates.

If you use the JPA cascading mechanism, you don't need to use DDL-level ON DELETE CASCADE, which can be a very dangerous operation if you remove a root entity that has many child entities on multiple levels.

Solution 5 - Hibernate

The equivalent JPA mapping for the DDL ON DELETE CASCADE is cascade=CascadeType.REMOVE. Orphan removal means that dependent entities are removed when the relationship to their "parent" entity is destroyed. For example if a child is removed from a @OneToMany relationship without explicitely removing it in the entity manager.

Solution 6 - Hibernate

The difference is:

  • orphanRemoval = true: "Child" entity is removed when it's no longer referenced (its parent may not be removed).
  • CascadeType.REMOVE: "Child" entity is removed only when its "Parent" is removed.

Solution 7 - Hibernate

@GaryK answer is absolutely great, I've spent an hour looking for an explanation orphanRemoval = true vs CascadeType.REMOVE and it helped me understand.

Summing up: orphanRemoval = true works identical as CascadeType.REMOVE ONLY IF we deleting object (entityManager.delete(object)) and we want the childs objects to be removed as well.

In completely different sitiuation, when we fetching some data like List<Child> childs = object.getChilds() and then remove a child (entityManager.remove(childs.get(0)) using orphanRemoval=true will cause that entity corresponding to childs.get(0) will be deleted from database.

Solution 8 - Hibernate

orphan removal has the same effect as ON DELETE CASCADE in the following scenario:- Lets say we have a simple many to one relationship between student entity and a guide entity, where many students can be mapped to the same guide and in database we have a foreign key relation between Student and Guide table such that student table has id_guide as FK.

    @Entity
    @Table(name = "student", catalog = "helloworld")
    public class Student implements java.io.Serializable {
	 @Id
     @GeneratedValue(strategy = IDENTITY)
     @Column(name = "id")
     private Integer id;

  	@ManyToOne(cascade={CascadeType.PERSIST,CascadeType.REMOVE})
    @JoinColumn(name = "id_guide")
    private Guide guide;

// The parent entity

    @Entity
    @Table(name = "guide", catalog = "helloworld")
    public class Guide implements java.io.Serializable {

/**
 * 
 */
private static final long serialVersionUID = 9017118664546491038L;

@Id
@GeneratedValue(strategy = IDENTITY)
@Column(name = "id", unique = true, nullable = false)
private Integer id;

@Column(name = "name", length = 45)
private String name;

@Column(name = "salary", length = 45)
private String salary;


 @OneToMany(mappedBy = "guide", orphanRemoval=true) 
 private Set<Student> students = new  HashSet<Student>(0);

In this scenario, the relationship is such that student entity is the owner of the relationship and as such we need to save the student entity in order to persist the whole object graph e.g.

    Guide guide = new Guide("John", "$1500");
	Student s1 = new Student(guide, "Roy","ECE");
	Student s2 = new Student(guide, "Nick", "ECE");
	em.persist(s1);
	em.persist(s2);

Here we are mapping the same guide with two different student objects and since the CASCADE.PERSIST is used , the object graph will be saved as below in the database table(MySql in my case)

STUDENT table:- ID Name Dept Id_Guide

1     Roy     ECE    1

2    Nick    ECE    1

GUIDE Table:-

ID NAME Salary

1    John     $1500

and Now if I want to remove one of the students, using

      Student student1 = em.find(Student.class,1);
	  em.remove(student1);

and when a student record is removed the corresponding guide record should also be removed, that's where CASCADE.REMOVE attribute in the Student entity comes into picture and what it does is ;it removes the student with identifier 1 as well the corresponding guide object(identifier 1). But in this example, there is one more student object which is mapped to the same guide record and unless we use the orphanRemoval=true attribute in the Guide Entity , the remove code above will not work.

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionMarkos FragkakisView Question on Stackoverflow
Solution 1 - HibernateaxtavtView Answer on Stackoverflow
Solution 2 - HibernateforhasView Answer on Stackoverflow
Solution 3 - HibernateOnurView Answer on Stackoverflow
Solution 4 - HibernateVlad MihalceaView Answer on Stackoverflow
Solution 5 - HibernateHeriView Answer on Stackoverflow
Solution 6 - Hibernateuser3572554View Answer on Stackoverflow
Solution 7 - HibernatepzeszkoView Answer on Stackoverflow
Solution 8 - HibernatekunalView Answer on Stackoverflow