Encodera

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.

Оглавление

Каскады и удаление сирот

В уроке про «многие к одному» каждый комментарий приходилось передавать в 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.

ЗначениеКакая операция передаётся
PERSISTpersist
MERGEmerge
REMOVEremove
REFRESH, DETACHrefresh, 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=?
--- статья удалена
  1. Один persist(post) сохранил статью и все три комментария.
  2. Удаление комментария из списка привело к delete — это работа orphanRemoval.
  3. 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))"; ...

Статью спасло только то, что на неё ссылаются другие комментарии. Ставьте каскады на коллекцию родителя и никогда — на ссылку ребёнка на родителя.

Спасибо, что читаете!

Пожертвуйте сайту, мы стараемся ради вас.