Обновление в некотором нетерпеливом n-n-отношении появляется в обоих связанных объектах в JPA - PullRequest
1 голос
/ 21 марта 2011

Каков наилучший способ изменить отношения n-n-n между двумя сущностями в JPA.

Позвольте мне объяснить на примере: предположим, у меня есть и сущность Author и Book,где Book может иметь различные Author с, а Author может писать на различных Book с.Эта модель представлена ​​в виде отношения n-to-n:

@Entity
public class Author {
    @ManyToMany(fetch=FetchType.EAGER)
    private List<Book> books;
    // ...
}

Также:

@Entity
public class Book {
    @ManyToMany(fetch=FetchType.EAGER)
    private List<Author> authors;
    // ...
}

Я хочу выполнить серию операций в этих объектах - например, пометитьAuthor в качестве автора Book:

public class SomeService {
    public void addAuthor(Book book, Author author)  {
        book.getAuthors().add(author);
        em.persist(book);
    }
}

или удаления Book из книг Author:

public class SomeOtherService {
    public void removeBook(Author author, Book book) {
        author.getBooks().remove(book);
        em.persist(author);
    }
}

Однако я быхотел бы, чтобы обновление было зарегистрировано в сущности, которая также не обновляется.Когда я звоню addAuthor(), я ожидаю, что обновленный Book также будет присутствовать в списке книг Author.Я хочу, чтобы Author был удален из списка Book авторов при вызове removeBook().

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

public class SomeService {
    public void addAuthor(Book book, Author author)  {
        book.getAuthors().add(author);
        author.getBooks().add(book);
        em.persist(book);
        em.persist(author);
    }
}

Хотя это выглядит для меня неудачным решением.Итак, мой вопрос: есть ли лучший способ сделать это?

Заранее спасибо?

Ответы [ 4 ]

1 голос
/ 22 марта 2011

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

1 голос
/ 22 марта 2011

Для меня это не очень плохое решение, так как в нем очень четко указано, что происходит, преимущество в том, что у вас есть очень четкий код. Однако, если для вас это слишком явно, у вас есть несколько альтернатив.

Как предположил Джей, вы можете написать метод обратного вызова @PreUpdate, который выполняет:

author.getBooks().add(book);

, чтобы это работало, однако вы должны каскадно вносить изменения в авторов, используя атрибут каскада аннотации @ManyToMany

@ManyToMany(cascade=CascadeType.ALL)

, например, или вы можете увеличить гранулярность CascadeType.

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

public void addAuthor(Author A) {
this.getAuthors().add(a);
a.addBook(this);
}

тогда вам потребуется реализовать addBook. И каскадные изменения.

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

1 голос
/ 22 марта 2011

Поддержание состояния двунаправленного отношения может быть сложной задачей.В конце должны быть вызваны оба

book.getAuthors().add(author);
author.getBooks().add(book);

.

Там, где я работаю, мы используем аспектJ с некоторыми внутренними метаданными для обработки параметров обеих сторон ассоциации.Для небольших проектов это немного излишне, и то, как вы это делаете, прекрасно.

0 голосов
/ 22 марта 2011

Посмотрите аннотации @PreXXXX & @PostXXXX. В зависимости от используемой вами БД, вам лучше пойти и написать свои собственные триггеры.

Если вы ищете самый независимый подход, решение, которое вы предложили, должно сработать.

Edit: Другой подход - сохранить код триггера и запустить его как собственный SQL после CRUD (при условии, что вы создали и удалили свои тесты).

...