Рубрики
Без рубрики

Руководство для начинающих по типам каскадов JPA и гибернации

Автор оригинала: Vlad Mihalcea.

JPA переводит переходы состояния сущности в базу данных DML операторы. Поскольку обычно используется для работы с графами сущностей, JPA позволяет нам распространять изменения состояния сущностей от Родителей к Дочерним сущностям.

Это поведение настраивается с помощью сопоставлений CascadeType .

Hibernate поддерживает все типы каскадов JPA и некоторые дополнительные устаревшие стили каскадирования. В следующей таблице показана связь между типами JPA Каскад и их Гибернацией собственным API эквивалентом:

Прослушиватель событий выселения по умолчанию ОТСОЕДИНИТЬ отсоединить(сущность) ОТСОЕДИНИТЬ или ВЫСЕЛИТЬ выселить(юридическое лицо)
Прослушиватель событий слияния по умолчанию ПОГЛОЩАТЬ слияние(сущность) ПОГЛОЩАТЬ слияние(сущность)
Прослушиватель постоянных событий по умолчанию НАСТАИВАТЬ сохраниться(сущность) НАСТАИВАТЬ сохраниться(сущность)
Прослушиватель событий Обновления по умолчанию ОСВЕЖИТЬ обновить(сущность) ОСВЕЖИТЬ обновить(сущность)
Прослушиватель событий Удаления по умолчанию УДАЛИТЬ удалить(сущность) УДАЛИТЬ или УДАЛИТЬ удалить(сущность)
Прослушиватель Событий Сохранения Или Обновления По Умолчанию СОХРАНИТЬ ОБНОВЛЕНИЕ Сохранить обновление(сущность)
Прослушиватель событий Репликации по Умолчанию КОПИРОВАТЬ репликация(сущность, режим репликации)
Прослушиватель событий Блокировки по умолчанию блокировка(сущность, тип блокировки) ЗАМОК buildLockRequest(сущность, блокировки)
ВСЕ Все вышеперечисленные методы EntityManager ВСЕ Все вышеперечисленные методы сеанса гибернации

Из этой таблицы мы можем сделать вывод, что:

  • Нет никакой разницы между вызовом сохраниться , объединить или обновить в JPA EntityManager или Спящий режим | Сеанс .
  • JPA удалить и отсоединить вызовы делегируются Спящий режим |/удаление и удаление собственных операций. Только Спящий режим
  • поддерживает репликацию и Сохранение/обновление . Хотя репликация полезна для некоторых очень специфических сценариев (когда точное состояние сущности должно быть отражено между двумя различными источниками данных ), комбинация сохранение и слияние всегда является лучшей альтернативой, чем собственная операция saveOrUpdate . Как правило, вы всегда должны использовать persist

    для ПЕРЕХОДНЫХ сущностей и объединять для ОТДЕЛЕННЫХ сущностей. Недостатки saveOrUpdate

    (при передаче снимка отсоединенной сущности в Сеанс , уже управляющий этой сущностью) привели к слиянию предшественнику операции: ныне исчезнувшей saveOrUpdateCopy операции. Метод JPA

  • блокировки ведет себя так же, как и метод запроса Hibernate блокировки. Каскадный тип JPA
  • |/.ВСЕ применимо не только к EntityManager операциям изменения состояния, но и ко всем Спящий режим Каскадные типы также. Поэтому, если вы сопоставили свои ассоциации с каскадным типом.ВСЕ , вы все еще можете каскадировать

    Спящий режим конкретные события. Например, вы можете каскадировать операцию блокировки JPA (хотя она ведет себя как повторное подключение, а не как фактическое распространение запроса на блокировку), даже если JPA не определяет тип каскада.ЗАМОК .

Каскадирование имеет смысл только для РодительскихДочерних ассоциаций (переход состояния Родительской сущности каскадируется в дочерние сущности). Каскадирование из Дочернего в Родительский не очень полезно, и обычно это запах кода сопоставления.

Далее я собираюсь проанализировать каскадное поведение всех JPA | Родительских Дочерних ассоциаций.

Один К Одному

Наиболее распространенная Взаимно однозначная двунаправленная ассоциация выглядит следующим образом:

@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(PostDetails details) {
        if (details != null) {
            details.setPost(null);
        }
        this.details = null;
    }
}

@Entity
public class PostDetails {

    @Id
    private Long id;

    @Column(name = "created_on")
    @Temporal(TemporalType.TIMESTAMP)
    private Date createdOn = new Date();

    private boolean visible;

    @OneToOne
    @MapsId
    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 играет Родительскую роль, а Сведения о публикации является Дочерним .

Двунаправленные ассоциации всегда должны обновляться с обеих сторон, поэтому Родительская сторона должна содержать комбинацию addChild и removeChild . Эти методы гарантируют, что мы всегда синхронизируем обе стороны связи, чтобы избежать проблем с повреждением объектов или реляционных данных.

В данном конкретном случае тип каскадный.ВСЕ и orphanremoval имеют смысл, потому что Сведения о публикации жизненный цикл привязан к Публикации Родительской сущности.

Каскадирование операции сохранения один к одному

Тип каскада .СОХРАНЯЕМЫЙ поставляется вместе с типом каскада.ВСЯ конфигурация , поэтому нам нужно сохранить только сущность Post , и связанная с ней сущность PostDetails также сохраняется:

Post post = new Post();
post.setName("Hibernate Master Class");

PostDetails details = new PostDetails();

post.addDetails(details);

session.persist(post);

Создание следующего вывода:

INSERT INTO post(id, NAME) 
VALUES (DEFAULT, Hibernate Master Class'')

insert into PostDetails (id, created_on, visible) 
values (1, '2015-03-03 10:17:19.14', false)

Каскадирование операции слияния один к одному

Тип каскада .СЛИЯНИЕ наследуется от каскадного типа.ВСЕ настройки , поэтому нам нужно только объединить сущность Post и связанные PostDetails также объединены:

Post post = newPost();
post.setName("Hibernate Master Class Training Material");
post.getDetails().setVisible(true);

doInTransaction(session -> {
    session.merge(post);
});

Операция слияния генерирует следующие выходные данные:

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.ВСЕ конфигурация, поэтому удаление Записи сущности также вызывает удаление записей сущности:

Post post = newPost();

doInTransaction(session -> {
    session.delete(post);
});

Создание следующего вывода:

delete from PostDetails where id = 1
delete from Post where id = 1

Каскадная операция удаления сирот один к одному

Если Дочерняя сущность отделена от своего Родителя , Дочерний внешний ключ имеет значение NULL . Если мы также хотим удалить Дочернюю строку, мы должны использовать поддержку orphanremoval .

doInTransaction(session -> {
    Post post = (Post) session.get(Post.class, 1L);
    post.removeDetails();
});

Удаление сироты генерирует этот вывод:

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 ), поскольку Дочерняя сущность контролирует ассоциацию через свой Внешний ключ. Но каскад не ограничивается двунаправленными ассоциациями, мы также можем использовать его для однонаправленных отношений:

@Entity
public class Commit {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    private String comment;

    @OneToOne(cascade = CascadeType.ALL)
    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;
    }
}

Каскадирование заключается в распространении перехода состояния Родительской сущности на одну или несколько Дочерних сущностей и может использоваться как для однонаправленных, так и для двунаправленных ассоциаций.

Один Ко Многим

Наиболее распространенная РодительскаяДочерняя ассоциация состоит из один ко многим и многие к одному отношения, где каскад полезен только для один ко многим стороны:

@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 comments = new ArrayList<>();

    public void setName(String name) {
        this.name = name;
    }

    public List 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;
    }
}

Как в примере один к одному , тип каскада .ВСЕ и orphanremoval подходят, потому что Комментарий жизненный цикл привязан к Сообщению |/Родительской сущности.

Каскадирование операции сохранения “один ко многим”

Нам нужно только сохранить Запись сущность, и все связанные Комментарии сущности также сохраняются:

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);

Операция сохранения генерирует следующий вывод:

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 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);
});

Создание следующего вывода:

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 post = newPost();

doInTransaction(session -> {
    session.delete(post);
});

Создание следующего вывода:

delete from Comment where id = 1
delete from Comment where id = 2
delete from Post where id = 1

Каскадная операция “один ко многим” по удалению сироты

orphanremoval позволяет нам удалять Дочернюю сущность всякий раз, когда на нее больше не ссылается ее Родитель :

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));
});

Комментарий удален, как мы видим в следующем выводе:

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.ВСЕ потому что CascadeType.REMOVE может привести к удалению большего количества, чем мы ожидаем (как вы скоро узнаете):

@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 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.getAuthors().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 authors = new ArrayList<>();

    private Book() {}

    public Book(String title) {
        this.title = title;
    }

    public List getAuthors() {
        retrun authors;
    }
}

Каскадирование операции сохранения “многие ко многим”

Сохранение Автора сущностей также сохранит Книги :

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_Author строки вставляются вместе с Авторами :

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 (2, 3)

Разъединение одной стороны ассоциации “многие ко многим”

Чтобы удалить Автора , нам нужно отделить все Book_Author отношения, принадлежащие удаляемому объекту:

doInTransaction(session -> {
    Author _Mark_Armstrong =
        getByName(session, "Mark Armstrong");
    _Mark_Armstrong.remove();
    session.delete(_Mark_Armstrong);
});

Этот вариант использования генерирует следующие выходные данные:

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.ВСЕ , но удаление сущности применяется не только к таблице ссылок, но и к другой стороне ассоциации.

Давайте изменим ассоциацию Автор сущность книги |/многие ко многим , чтобы использовать Каскадный тип.ВСЕ вместо:

@ManyToMany(mappedBy = "authors", cascade = CascadeType.ALL)
private List books = new ArrayList<>();

При удалении одного Автора :

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());
});

Все книги, принадлежащие удаленному Автору , удаляются, даже если другие Авторы мы все еще связаны с удаленными Книгами :

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

Чаще всего такое поведение не соответствует ожиданиям бизнес-логики и обнаруживается только при первом удалении сущности.

Мы можем продвинуть эту проблему еще дальше, если установим Каскадный тип.ВСЕ на сторону Книги сущности, а также:

@ManyToMany(cascade = CascadeType.ALL)
@JoinTable(name = "Book_Author",
    joinColumns = {
        @JoinColumn(
            name = "book_id", 
            referencedColumnName = "id"
        )
    },
    inverseJoinColumns = {
        @JoinColumn(
            name = "author_id", 
            referencedColumnName = "id"
        )
    }
)

На этот раз удаляются не только Книги , но и Авторы :

doInTransaction(session -> {
    Author _Mark_Armstrong = 
        getByName(session, "Mark Armstrong");
    session.delete(_Mark_Armstrong);
    Author _John_Smith = 
        getByName(session, "John Smith");
    assertNull(_John_Smith);
});

Удаление Автора приводит к удалению всех связанных Книг , что в дальнейшем приводит к удалению всех связанных Авторов . Это очень опасная операция, приводящая к массовому удалению сущности, что редко является ожидаемым поведением.

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 утверждений, и в конечном итоге мы удаляем всех авторов и все их книги. Вот почему Каскадный тип.ВСЕ должны поднимать брови всякий раз, когда вы замечаете это в ассоциации “многие ко многим”//.

Когда дело доходит до Спящий режим сопоставления, вы всегда должны стремиться к простоте. Документация Hibernate также подтверждает это предположение:

Практические тестовые примеры для реальных многие ко многим ассоциаций встречаются редко. Большую часть времени вам нужна дополнительная информация, хранящаяся в таблице ссылка . В этом случае гораздо лучше использовать две ассоциации один ко многим для класса промежуточной связи. На самом деле, большинство ассоциаций являются один ко многим и многие к одному . По этой причине вам следует действовать осторожно при использовании любого другого стиля ассоциации.

Каскадирование-удобная функция ORM , но она не свободна от проблем. Вы должны переходить только от родительских сущностей к Дочерним , а не наоборот. Вы всегда должны использовать только операции casacde, которые требуются в соответствии с требованиями вашей бизнес-логики, а не включать каскадный тип.ВСЕ в конфигурацию распространения состояния объекта ассоциации по умолчанию РодительскийДочерний .

Код доступен на GitHub .