Вступление
JPA переводит переходы состояния сущности в операторы базы данных DML . Поскольку работать с графами сущностей обычно, JPA позволяет нам распространять изменения состояния сущностей с родительских на дочерние сущности.
Это поведение настраивается с помощью сопоставлений CascadeType .
JPA против Hibernate Каскад Типы
Hibernate поддерживает все каскадные типы JPA и некоторые дополнительные устаревшие каскадные стили. В следующей таблице показана связь между типами JPA Cascade и их эквивалентом в Hibernate:
Из этой таблицы можно сделать вывод, что:
- Нет разницы между вызовами persist , merge или refresh в JPA EntityManager или Hibernate Session .
- Вызовы JPA для удаления и отсоединения делегируются Hibernate для удаления и удаления собственных операций.
- Только Hibernate поддерживает репликацию и saveOrUpdate . В то время как репликация полезна для некоторых очень специфических сценариев (когда точное состояние объекта должно быть отражено между двумя различными источниками данных), комбинация persist и merge всегда является лучшей альтернативой, чем собственная операция saveOrUpdate. Как правило, вы должны всегда используйте постоянство для объектов TRANSIENT и объединение для объектов DETACHED. Недостатки saveOrUpdate (при передаче снимка отдельного объекта сеансу, уже управляющему этим объектом) привели к предшественнику операции объединения : теперь уже потухшая операция saveOrUpdateCopy .
- Метод блокировки JPA использует то же поведение, что и метод запроса блокировки Hibernate.
- JPA CascadeType.ALL применяется не только к операциям изменения состояния EntityManager , но и ко всем Hibernate CascadeTypes. Так что если вы сопоставили свои ассоциации с CascadeType.ALL , вы все равно можете каскадировать определенные события Hibernate. Например, вы можете каскадировать операцию блокировки JPA (хотя она ведет себя как присоединение, а не фактическое распространение запроса блокировки), даже если JPA не определяет LOCK CascadeType .
Каскадные лучшие практики
Каскадирование имеет смысл только для родительско- дочерних ассоциаций (переход состояния родительского объекта каскадно связан с его дочерними объектами). Каскадирование от Ребенка к Родителю не очень полезно и, как правило, это запах кода отображения.
Далее я собираюсь проанализировать каскадное поведение всех ассоциаций родительского и дочернего JPA.
Один к одному
Наиболее распространенная двусторонняя ассоциация «один к одному» выглядит следующим образом:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
|
@Entity public class Post { @Id @GeneratedValue (strategy = GenerationType.AUTO) private Long id; private String name; @OneToOne (mappedBy = "post" , cascade = CascadeType.ALL, orphanRemoval = true ) private PostDetails details; public Long getId() { return id; } public PostDetails getDetails() { return details; } public String getName() { return name; } public void setName(String name) { this .name = name; } public void addDetails(PostDetails details) { this .details = details; details.setPost( this ); } public void removeDetails() { if (details != null ) { details.setPost( null ); } this .details = null ; } } @Entity public class PostDetails { @Id @GeneratedValue (strategy = GenerationType.AUTO) private Long id; @Column (name = "created_on" ) @Temporal (TemporalType.TIMESTAMP) private Date createdOn = new Date(); private boolean visible; @OneToOne @PrimaryKeyJoinColumn private Post post; public Long getId() { return id; } public void setVisible( boolean visible) { this .visible = visible; } public void setPost(Post post) { this .post = post; } } |
Сущность Post играет роль Parent, а PostDetails — это Child .
Двунаправленные ассоциации всегда должны обновляться с обеих сторон, поэтому родительская сторона должна содержать комбинацию addChild и removeChild . Эти методы гарантируют, что мы всегда синхронизируем обе стороны ассоциации, чтобы избежать проблем повреждения данных объекта или реляционной.
В этом конкретном случае удаление CascadeType.ALL и потерянных объектов имеет смысл, поскольку жизненный цикл PostDetails связан с жизненным циклом его объекта Post Parent .
Каскадная персистентная операция один-к-одному
CascadeType.PERSIST поставляется вместе с конфигурацией CascadeType.ALL , поэтому нам нужно только сохранить сущность Post , и связанная сущность PostDetails также сохраняется:
1
2
3
4
5
6
7
8
|
Post post = new Post(); post.setName( "Hibernate Master Class" ); PostDetails details = new PostDetails(); post.addDetails(details); session.persist(post); |
Генерация следующего вывода:
1
2
3
4
5
|
INSERT INTO post(id, NAME) VALUES (DEFAULT, Hibernate Master Class '' ) insert into PostDetails (id, created_on, visible) values ( default , '2015-03-03 10:17:19.14' , false ) |
Каскадная операция слияния один-к-одному
CascadeType.MERGE наследуется от настройки CascadeType.ALL , поэтому нам нужно только объединить сущность Post, а также объединить связанные PostDetails :
1
2
3
4
5
6
7
|
Post post = newPost(); post.setName( "Hibernate Master Class Training Material" ); post.getDetails().setVisible( true ); doInTransaction(session -> { session.merge(post); }); |
Операция объединения генерирует следующий вывод:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
SELECT onetooneca0_.id AS id1_3_1_, onetooneca0_. NAME AS name2_3_1_, onetooneca1_.id AS id1_4_0_, onetooneca1_.created_on AS created_2_4_0_, onetooneca1_.visible AS visible3_4_0_ FROM post onetooneca0_ LEFT OUTER JOIN postdetails onetooneca1_ ON onetooneca0_.id = onetooneca1_.id WHERE onetooneca0_.id = 1 UPDATE postdetails SET created_on = '2015-03-03 10:20:53.874' , visible = true WHERE id = 1 UPDATE post SET NAME = 'Hibernate Master Class Training Material' WHERE id = 1 |
Каскадная операция удаления «один к одному»
CascadeType.REMOVE также наследуется от конфигурации CascadeType.ALL , поэтому удаление сущности Post также инициирует удаление сущности PostDetails :
1
2
3
4
5
|
Post post = newPost(); doInTransaction(session -> { session.delete(post); }); |
Генерация следующего вывода:
1
2
|
delete from PostDetails where id = 1 delete from Post where id = 1 |
Один-к-одному удалить потерянную каскадную операцию
Если дочерний объект отделен от своего родительского объекта, для дочернего внешнего ключа устанавливается значение NULL. Если мы также хотим удалить дочернюю строку, мы должны использовать поддержку удаления потерянных объектов .
1
2
3
4
|
doInTransaction(session -> { Post post = (Post) session.get(Post. class , 1L); post.removeDetails(); }); |
Удаление сирот генерирует этот вывод:
01
02
03
04
05
06
07
08
09
10
11
|
SELECT onetooneca0_.id AS id1_3_0_, onetooneca0_. NAME AS name2_3_0_, onetooneca1_.id AS id1_4_1_, onetooneca1_.created_on AS created_2_4_1_, onetooneca1_.visible AS visible3_4_1_ FROM post onetooneca0_ LEFT OUTER JOIN postdetails onetooneca1_ ON onetooneca0_.id = onetooneca1_.id WHERE onetooneca0_.id = 1 delete from PostDetails where id = 1 |
Однонаправленная связь один-к-одному
Чаще всего родительский объект является обратной стороной (например, mappedBy ), а дочерний объект контролирует ассоциацию через свой внешний ключ. Но каскад не ограничивается двунаправленными ассоциациями, мы также можем использовать его для однонаправленных связей:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
|
@Entity public class Commit { @Id @GeneratedValue (strategy = GenerationType.AUTO) private Long id; private String comment; @OneToOne (cascade = CascadeType.ALL) @JoinTable ( name = "Branch_Merge_Commit" , joinColumns = @JoinColumn ( name = "commit_id" , referencedColumnName = "id" ), inverseJoinColumns = @JoinColumn ( name = "branch_merge_id" , referencedColumnName = "id" ) ) private BranchMerge branchMerge; public Commit() { } public Commit(String comment) { this .comment = comment; } public Long getId() { return id; } public void addBranchMerge( String fromBranch, String toBranch) { this .branchMerge = new BranchMerge( fromBranch, toBranch); } public void removeBranchMerge() { this .branchMerge = null ; } } @Entity public class BranchMerge { @Id @GeneratedValue (strategy = GenerationType.AUTO) private Long id; private String fromBranch; private String toBranch; public BranchMerge() { } public BranchMerge( String fromBranch, String toBranch) { this .fromBranch = fromBranch; this .toBranch = toBranch; } public Long getId() { return id; } } |
Каскадирование заключается в распространении перехода состояния родительского объекта на один или несколько дочерних объектов, и его можно использовать как для однонаправленных, так и для двунаправленных ассоциаций.
Один ко многим
Наиболее распространенная ассоциация Родитель — Ребенок состоит из отношений «один ко многим» и «многие к одному», где каскад полезен только для стороны «один ко многим»:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
|
@Entity public class Post { @Id @GeneratedValue (strategy = GenerationType.AUTO) private Long id; private String name; @OneToMany (cascade = CascadeType.ALL, mappedBy = "post" , orphanRemoval = true ) private List<Comment> comments = new ArrayList<>(); public void setName(String name) { this .name = name; } public List<Comment> getComments() { return comments; } public void addComment(Comment comment) { comments.add(comment); comment.setPost( this ); } public void removeComment(Comment comment) { comment.setPost( null ); this .comments.remove(comment); } } @Entity public class Comment { @Id @GeneratedValue (strategy = GenerationType.AUTO) private Long id; @ManyToOne private Post post; private String review; public void setPost(Post post) { this .post = post; } public String getReview() { return review; } public void setReview(String review) { this .review = review; } } |
Как и в примере «один-к-одному», CascadeType.ALL и удаление-сирота подходят, потому что жизненный цикл Comment привязан к жизненному циклу его объекта Post Parent .
Каскадная операция «один ко многим»
Нам нужно только сохранить сущность Post, и все связанные сущности Comment также будут сохранены:
01
02
03
04
05
06
07
08
09
10
11
12
|
Post post = new Post(); post.setName( "Hibernate Master Class" ); Comment comment1 = new Comment(); comment1.setReview( "Good post!" ); Comment comment2 = new Comment(); comment2.setReview( "Nice post!" ); post.addComment(comment1); post.addComment(comment2); session.persist(post); |
Операция persist генерирует следующий вывод:
1
2
3
4
5
6
7
8
|
insert into Post (id, name) values ( default , 'Hibernate Master Class' ) insert into Comment (id, post_id, review) values ( default , 1 , 'Good post!' ) insert into Comment (id, post_id, review) values ( default , 1 , 'Nice post!' ) |
Каскадная операция слияния один ко многим
Объединение сущности Post также объединит все сущности Comment :
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
Post post = newPost(); post.setName( "Hibernate Master Class Training Material" ); post.getComments() .stream() .filter(comment -> comment.getReview().toLowerCase() .contains( "nice" )) .findAny() .ifPresent(comment -> comment.setReview( "Keep up the good work!" ) ); doInTransaction(session -> { session.merge(post); }); |
Генерация следующего вывода:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
SELECT onetomanyc0_.id AS id1_1_1_, onetomanyc0_. NAME AS name2_1_1_, comments1_.post_id AS post_id3_1_3_, comments1_.id AS id1_0_3_, comments1_.id AS id1_0_0_, comments1_.post_id AS post_id3_0_0_, comments1_.review AS review2_0_0_ FROM post onetomanyc0_ LEFT OUTER JOIN comment comments1_ ON onetomanyc0_.id = comments1_.post_id WHERE onetomanyc0_.id = 1 update Post set name = 'Hibernate Master Class Training Material' where id = 1 update Comment set post_id = 1, review= 'Keep up the good work!' where id = 2 |
Каскадная операция удаления «один ко многим»
Когда сущность Post удаляется, связанные сущности Comment также удаляются:
1
2
3
4
5
|
Post post = newPost(); doInTransaction(session -> { session.delete(post); }); |
Генерация следующего вывода:
1
2
3
|
delete from Comment where id = 1 delete from Comment where id = 2 delete from Post where id = 1 |
Каскадная операция удаления «один ко многим»
Удаление-сирота позволяет нам удалять сущность Child, когда на нее больше не ссылается родительский объект:
01
02
03
04
05
06
07
08
09
10
11
12
|
newPost(); doInTransaction(session -> { Post post = (Post) session.createQuery( "select p " + "from Post p " + "join fetch p.comments " + "where p.id = :id" ) .setParameter( "id" , 1L) .uniqueResult(); post.removeComment(post.getComments().get( 0 )); }); |
Комментарий удален, как мы видим в следующем выводе:
01
02
03
04
05
06
07
08
09
10
11
12
13
|
SELECT onetomanyc0_.id AS id1_1_0_, comments1_.id AS id1_0_1_, onetomanyc0_. NAME AS name2_1_0_, comments1_.post_id AS post_id3_0_1_, comments1_.review AS review2_0_1_, comments1_.post_id AS post_id3_1_0__, comments1_.id AS id1_0_0__ FROM post onetomanyc0_ INNER JOIN comment comments1_ ON onetomanyc0_.id = comments1_.post_id WHERE onetomanyc0_.id = 1 delete from Comment where id = 1 |
Многие-ко-многим
Отношение многих ко многим сложно, потому что каждая сторона этой ассоциации играет роль Родителя и Дитя . Тем не менее, мы можем определить одну сторону, откуда мы хотели бы распространять изменения состояния сущности.
Мы не должны по умолчанию использовать CascadeType.ALL , потому что CascadeTpe.REMOVE может в конечном итоге удалить больше, чем мы ожидаем (как вы скоро узнаете):
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
|
@Entity public class Author { @Id @GeneratedValue (strategy=GenerationType.AUTO) private Long id; @Column (name = "full_name" , nullable = false ) private String fullName; @ManyToMany (mappedBy = "authors" , cascade = {CascadeType.PERSIST, CascadeType.MERGE}) private List<Book> books = new ArrayList<>(); private Author() {} public Author(String fullName) { this .fullName = fullName; } public Long getId() { return id; } public void addBook(Book book) { books.add(book); book.authors.add( this ); } public void removeBook(Book book) { books.remove(book); book.authors.remove( this ); } public void remove() { for (Book book : new ArrayList<>(books)) { removeBook(book); } } } @Entity public class Book { @Id @GeneratedValue (strategy=GenerationType.AUTO) private Long id; @Column (name = "title" , nullable = false ) private String title; @ManyToMany (cascade = {CascadeType.PERSIST, CascadeType.MERGE}) @JoinTable (name = "Book_Author" , joinColumns = { @JoinColumn ( name = "book_id" , referencedColumnName = "id" ) }, inverseJoinColumns = { @JoinColumn ( name = "author_id" , referencedColumnName = "id" ) } ) private List<Author> authors = new ArrayList<>(); private Book() {} public Book(String title) { this .title = title; } } |
Каскадная операция «многие ко многим»
Сохранение сущностей Автора также сохранит Книги :
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
Author _John_Smith = new Author( "John Smith" ); Author _Michelle_Diangello = new Author( "Michelle Diangello" ); Author _Mark_Armstrong = new Author( "Mark Armstrong" ); Book _Day_Dreaming = new Book( "Day Dreaming" ); Book _Day_Dreaming_2nd = new Book( "Day Dreaming, Second Edition" ); _John_Smith.addBook(_Day_Dreaming); _Michelle_Diangello.addBook(_Day_Dreaming); _John_Smith.addBook(_Day_Dreaming_2nd); _Michelle_Diangello.addBook(_Day_Dreaming_2nd); _Mark_Armstrong.addBook(_Day_Dreaming_2nd); session.persist(_John_Smith); session.persist(_Michelle_Diangello); session.persist(_Mark_Armstrong); |
Строки Book и Book_Author вставляются вместе с авторами :
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
insert into Author (id, full_name) values ( default , 'John Smith' ) insert into Book (id, title) values ( default , 'Day Dreaming' ) insert into Author (id, full_name) values ( default , 'Michelle Diangello' ) insert into Book (id, title) values ( default , 'Day Dreaming, Second Edition' ) insert into Author (id, full_name) values ( default , 'Mark Armstrong' ) insert into Book_Author (book_id, author_id) values ( 1 , 1 ) insert into Book_Author (book_id, author_id) values ( 1 , 2 ) insert into Book_Author (book_id, author_id) values ( 2 , 1 ) insert into Book_Author (book_id, author_id) values ( 2 , 2 ) insert into Book_Author (book_id, author_id) values ( 3 , 1 ) |
Разобщение одной стороны ассоциации «многие ко многим»
Чтобы удалить Автора , нам нужно отделить все отношения Book_Author, принадлежащие съемному объекту:
1
2
3
4
5
6
|
doInTransaction(session -> { Author _Mark_Armstrong = getByName(session, "Mark Armstrong" ); _Mark_Armstrong.remove(); session.delete(_Mark_Armstrong); }); |
Этот вариант использования генерирует следующий вывод:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
SELECT manytomany0_.id AS id1_0_0_, manytomany2_.id AS id1_1_1_, manytomany0_.full_name AS full_nam2_0_0_, manytomany2_.title AS title2_1_1_, books1_.author_id AS author_i2_0_0__, books1_.book_id AS book_id1_2_0__ FROM author manytomany0_ INNER JOIN book_author books1_ ON manytomany0_.id = books1_.author_id INNER JOIN book manytomany2_ ON books1_.book_id = manytomany2_.id WHERE manytomany0_.full_name = 'Mark Armstrong' SELECT books0_.author_id AS author_i2_0_0_, books0_.book_id AS book_id1_2_0_, manytomany1_.id AS id1_1_1_, manytomany1_.title AS title2_1_1_ FROM book_author books0_ INNER JOIN book manytomany1_ ON books0_.book_id = manytomany1_.id WHERE books0_.author_id = 2 delete from Book_Author where book_id = 2 insert into Book_Author (book_id, author_id) values (2, 1) insert into Book_Author (book_id, author_id) values (2, 2) delete from Author where id = 3 |
Ассоциация «многие ко многим» генерирует слишком много избыточных операторов SQL, и зачастую их очень трудно настроить. Далее я собираюсь продемонстрировать скрытые опасности «многие ко многим» CascadeType.REMOVE .
Много-ко-многим CascadeType.REMOVE есть ошибки
CascadeType.ALL «многие ко многим» — это еще один запах кода, с которым я часто сталкиваюсь при просмотре кода. CascadeType.REMOVE автоматически наследуется при использовании CascadeType.ALL , но удаление сущностей применяется не только к таблице ссылок, но и к другой стороне ассоциации.
Давайте заменим ассоциацию «многие ко многим» книги « Автор», чтобы вместо нее использовать CascadeType.ALL :
1
2
3
|
@ManyToMany (mappedBy = "authors" , cascade = CascadeType.ALL) private List<Book> books = new ArrayList<>(); |
При удалении одного автора :
1
2
3
4
5
6
7
8
|
doInTransaction(session -> { Author _Mark_Armstrong = getByName(session, "Mark Armstrong" ); session.delete(_Mark_Armstrong); Author _John_Smith = getByName(session, "John Smith" ); assertEquals( 1 , _John_Smith.books.size()); }); |
Все книги, принадлежащие удаленному автору , удаляются, даже если другие авторы все еще связаны с удаленными книгами :
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
|
SELECT manytomany0_.id AS id1_0_, manytomany0_.full_name AS full_nam2_0_ FROM author manytomany0_ WHERE manytomany0_.full_name = 'Mark Armstrong' SELECT books0_.author_id AS author_i2_0_0_, books0_.book_id AS book_id1_2_0_, manytomany1_.id AS id1_1_1_, manytomany1_.title AS title2_1_1_ FROM book_author books0_ INNER JOIN book manytomany1_ ON books0_.book_id = manytomany1_.id WHERE books0_.author_id = 3 delete from Book_Author where book_id=2 delete from Book where id=2 delete from Author where id=3 |
Чаще всего такое поведение не соответствует ожиданиям бизнес-логики, а обнаруживается только при первом удалении объекта.
Мы можем продвинуть эту проблему еще дальше, если мы установим CascadeType.ALL на стороне сущности Book :
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
@ManyToMany (cascade = CascadeType.ALL) @JoinTable (name = "Book_Author" , joinColumns = { @JoinColumn ( name = "book_id" , referencedColumnName = "id" ) }, inverseJoinColumns = { @JoinColumn ( name = "author_id" , referencedColumnName = "id" ) } ) |
На этот раз удаляются не только Книги , но и Авторы :
1
2
3
4
5
6
7
8
|
doInTransaction(session -> { Author _Mark_Armstrong = getByName(session, "Mark Armstrong" ); session.delete(_Mark_Armstrong); Author _John_Smith = getByName(session, "John Smith" ); assertNull(_John_Smith); }); |
Удаление Автор инициирует удаление всех связанных Книг , что дополнительно вызывает удаление всех связанных Авторов . Это очень опасная операция, в результате которой происходит массовое удаление объектов, что редко приводит к ожидаемому поведению.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
|
SELECT manytomany0_.id AS id1_0_, manytomany0_.full_name AS full_nam2_0_ FROM author manytomany0_ WHERE manytomany0_.full_name = 'Mark Armstrong' SELECT books0_.author_id AS author_i2_0_0_, books0_.book_id AS book_id1_2_0_, manytomany1_.id AS id1_1_1_, manytomany1_.title AS title2_1_1_ FROM book_author books0_ INNER JOIN book manytomany1_ ON books0_.book_id = manytomany1_.id WHERE books0_.author_id = 3 SELECT authors0_.book_id AS book_id1_1_0_, authors0_.author_id AS author_i2_2_0_, manytomany1_.id AS id1_0_1_, manytomany1_.full_name AS full_nam2_0_1_ FROM book_author authors0_ INNER JOIN author manytomany1_ ON authors0_.author_id = manytomany1_.id WHERE authors0_.book_id = 2 SELECT books0_.author_id AS author_i2_0_0_, books0_.book_id AS book_id1_2_0_, manytomany1_.id AS id1_1_1_, manytomany1_.title AS title2_1_1_ FROM book_author books0_ INNER JOIN book manytomany1_ ON books0_.book_id = manytomany1_.id WHERE books0_.author_id = 1 SELECT authors0_.book_id AS book_id1_1_0_, authors0_.author_id AS author_i2_2_0_, manytomany1_.id AS id1_0_1_, manytomany1_.full_name AS full_nam2_0_1_ FROM book_author authors0_ INNER JOIN author manytomany1_ ON authors0_.author_id = manytomany1_.id WHERE authors0_.book_id = 1 SELECT books0_.author_id AS author_i2_0_0_, books0_.book_id AS book_id1_2_0_, manytomany1_.id AS id1_1_1_, manytomany1_.title AS title2_1_1_ FROM book_author books0_ INNER JOIN book manytomany1_ ON books0_.book_id = manytomany1_.id WHERE books0_.author_id = 2 delete from Book_Author where book_id=2 delete from Book_Author where book_id=1 delete from Author where id=2 delete from Book where id=1 delete from Author where id=1 delete from Book where id=2 delete from Author where id=3 |
Этот вариант использования неверен во многих отношениях. Существует множество ненужных операторов SELECT, и в итоге мы удаляем всех авторов и все их книги. Вот почему CascadeType.ALL должен поднять бровь, когда вы заметите это в ассоциации «многие ко многим».
Когда дело доходит до отображений Hibernate, вы всегда должны стремиться к простоте. Документация Hibernate также подтверждает это предположение:
Практические тесты для реальных ассоциаций «многие ко многим» редки. Большую часть времени вам нужна дополнительная информация, хранящаяся в «таблице ссылок». В этом случае гораздо лучше использовать две связи «один ко многим» с промежуточным классом ссылок. На самом деле, большинство ассоциаций один-ко-многим и многие-к-одному. По этой причине вам следует действовать осторожно при использовании любого другого стиля ассоциации.
Вывод
Каскадирование — это удобная функция ORM, но она не свободна от проблем. Вы должны только каскадно переходить от Родительских сущностей к Детям, а не наоборот. Вы должны всегда использовать только те операции casacde, которые требуются вашими требованиями бизнес-логики, и не превращать CascadeType.ALL в стандартную конфигурацию распространения состояния сущности «родитель-потомок».
- Код доступен на GitHub .
Ссылка: | Руководство для начинающих по JPA и Hibernate Cascade Types от нашего партнера по JCG Влада Михалча в блоге Влада Михалчиа . |