Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.


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.


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.


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.


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.


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.