Каскады и удаление сирот
В уроке про «многие к одному» каждый комментарий приходилось передавать в persist отдельно. Каскад (cascade) передаёт операцию от одной сущности связанным: сохранили статью — сохранились и комментарии, удалили статью — удалились и комментарии. Удаление сирот (orphan removal) идёт дальше: комментарий, убранный из списка статьи, удаляется из базы.
Включение
Меняется только список в Post:
// 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 импортируется из jakarta.persistence.
| Значение | Какая операция передаётся |
|---|---|
PERSIST | persist |
MERGE | merge |
REMOVE | remove |
REFRESH, DETACH | refresh, detach |
ALL | Все перечисленные |
Три операции
// src/main/java/com/example/blog/Main.java
emf.runInTransaction(em -> {
Post post = new Post("Каскады");
post.addComment(new Comment("Первый!"));
post.addComment(new Comment("Спам, купите сейчас"));
post.addComment(new Comment("Очень полезно"));
em.persist(post);
});
IO.println("--- сохранено");
emf.runInTransaction(em -> {
Post post = em.find(Post.class, 1L);
Comment spam = post.getComments().get(1);
post.removeComment(spam);
});
IO.println("--- спам удалён");
emf.runInTransaction(em -> em.remove(em.find(Post.class, 1L)));
IO.println("--- статья удалена");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 (?,?,?)
--- сохранено
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: 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=?
--- статья удалена- Один
persist(post)сохранил статью и все три комментария. - Удаление комментария из списка привело к
delete— это работаorphanRemoval. remove(post)сначала удалил оставшиеся комментарии, потом саму статью, поэтому внешний ключ ни разу не нарушился.
Когда использовать
Каскады подходят для связи «родитель и его части», где части без родителя не имеют смысла: комментарии статьи, строки заказа. Не ставьте CascadeType.ALL на связи между независимыми сущностями, например статьи и тега или статьи и автора: удаление статьи не должно удалять её автора.
Без каскада комментарий, который только добавили в список, молча не сохраняется — ошибки нет, строка просто не появляется.
Частая ошибка
Каскад в обратную сторону — на @ManyToOne — почти всегда ошибка. С @ManyToOne(cascade = CascadeType.REMOVE) в Comment удаление одного комментария пытается удалить и его статью:
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))"; ...Статью спасло только то, что на неё ссылаются другие комментарии. Ставьте каскады на коллекцию родителя и никогда — на ссылку ребёнка на родителя.