Cascading and orphan removal
In the lesson on many-to-one every comment had to be passed to persist separately. Cascading passes an operation from one entity to the related ones: persist a post — its comments are persisted too, remove a post — its comments are removed. Orphan removal goes one step further: a comment taken out of the post's list is deleted from the database.
Turning it on
Only the list in Post changes:
// src/main/java/com/example/blog/Post.java
@OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Comment> comments = new ArrayList<>();
public void addComment(Comment comment) {
comments.add(comment);
comment.setPost(this);
}
public void removeComment(Comment comment) {
comments.remove(comment);
comment.setPost(null);
}CascadeType is imported from jakarta.persistence.
| Value | Which operation is passed on |
|---|---|
PERSIST | persist |
MERGE | merge |
REMOVE | remove |
REFRESH, DETACH | refresh, detach |
ALL | All of the above |
Three operations
// src/main/java/com/example/blog/Main.java
emf.runInTransaction(em -> {
Post post = new Post("Cascading");
post.addComment(new Comment("First!"));
post.addComment(new Comment("Spam, buy now"));
post.addComment(new Comment("Very helpful"));
em.persist(post);
});
IO.println("--- saved");
emf.runInTransaction(em -> {
Post post = em.find(Post.class, 1L);
Comment spam = post.getComments().get(1);
post.removeComment(spam);
});
IO.println("--- spam removed");
emf.runInTransaction(em -> em.remove(em.find(Post.class, 1L)));
IO.println("--- post removed");Hibernate: insert into Post (title,id) values (?,?)
Hibernate: insert into Comment (post_id,text,id) values (?,?,?)
Hibernate: insert into Comment (post_id,text,id) values (?,?,?)
Hibernate: insert into Comment (post_id,text,id) values (?,?,?)
--- saved
Hibernate: select p1_0.id,p1_0.title from Post p1_0 where p1_0.id=?
Hibernate: select c1_0.post_id,c1_0.id,c1_0.text from Comment c1_0 where c1_0.post_id=?
Hibernate: delete from Comment where id=?
--- spam removed
Hibernate: select p1_0.id,p1_0.title from Post p1_0 where p1_0.id=?
Hibernate: select c1_0.post_id,c1_0.id,c1_0.text from Comment c1_0 where c1_0.post_id=?
Hibernate: delete from Comment where id=?
Hibernate: delete from Comment where id=?
Hibernate: delete from Post where id=?
--- post removed- One
persist(post)saved the post and all three comments. - Removing a comment from the list caused a
delete— that isorphanRemoval. remove(post)first deleted the remaining comments, then the post itself, so the foreign key was never violated.
When to use it
Cascading suits a "parent and its parts" relationship, where the parts make no sense without the parent: comments of a post, lines of an order. Do not put CascadeType.ALL on links between independent entities, such as post and tag or post and author: deleting a post must not delete its author.
Without cascading, a comment that was only added to the list is silently not saved — there is no error, the row just does not appear.
Common mistake
Cascading in the opposite direction — on @ManyToOne — is almost always wrong. With @ManyToOne(cascade = CascadeType.REMOVE) in Comment, deleting one comment tries to delete its post too:
Hibernate: delete from Comment where id=?
Hibernate: delete from Post where id=?
Exception in thread "main" jakarta.persistence.RollbackException: Error while committing the transaction [could not execute statement [Referential integrity constraint violation: "FKQB0RNHT649IFUH6GEV5LWVX8X: PUBLIC.COMMENT FOREIGN KEY(POST_ID) REFERENCES PUBLIC.POST(ID) (CAST(1 AS BIGINT))"; ...The database saved the post only because other comments still refer to it. Put cascades on the parent's collection, never on the child's reference to the parent.