Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

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.

ValueWhich operation is passed on
PERSISTpersist
MERGEmerge
REMOVEremove
REFRESH, DETACHrefresh, detach
ALLAll 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
  1. One persist(post) saved the post and all three comments.
  2. Removing a comment from the list caused a delete — that is orphanRemoval.
  3. 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.

Thanks for reading!

Donate to the site — we work hard for you.