Гибернация «многие ко многим» с проблемой каскадирования в объединенном классе - PullRequest
0 голосов
/ 21 сентября 2018

У меня есть Many-to-Many отношение между классом Foo и Bar.Поскольку я хочу получить дополнительную информацию о вспомогательной таблице, мне пришлось создать вспомогательный класс FooBar, как описано здесь: Лучший способ отобразить связь «многие ко многим» с дополнительными столбцами при использовании JPA и Hibernate

Я создал Foo и создал несколько баров (сохраненных в БД).Когда я затем добавляю один из столбцов в foo, используя

foo.addBar(bar);            // adds it bidirectionally
barRepository.save(bar);    // JpaRepository

, создается запись DB для FooBar - как и ожидалось.

Но когда я хочу снова удалить этот же столбециз foo, используя

foo.removeBar(bar);         // removes it bidirectionally
barRepository.save(bar);    // JpaRepository

, тогда ранее созданная FooBar-запись NOT удалена из БД.С отладкой я увидел, что foo.removeBar(bar); действительно удалял двунаправленно.Нет исключений.

Я что-то не так делаю?Я совершенно уверен, что это связано с параметрами каскадирования, так как я сохраняю только бар.


Что я пробовал:

  • добавление orphanRemoval = true воба @OneToMany - аннотации, которые не работали.И я думаю, что это правильно, потому что я не удаляю ни Foo, ни Bar, только их отношение.

  • , исключая CascadeType.REMOVE из аннотаций @OneToMany, нотак же, как orphanRemoval, я думаю, что это не для этого случая.


Редактировать: Я подозреваю, что в моем коде или модели должно быть что-то, что мешает моему orphanRemoval, поскольку теперь уже есть 2 ответа, которые говорятчто это работает (с orphanRemoval=true).

На первоначальный вопрос ответили, но если кто-нибудь знает, что может заставить мой orphanRemoval не работать, я был бы очень признателен за ваш вклад.Спасибо


Код: Foo, Bar, FooBar

public class Foo {

    private Collection<FooBar> fooBars = new HashSet<>();

    // constructor omitted for brevity

    @OneToMany(cascade = CascadeType.ALL, mappedBy = "foo", fetch = FetchType.EAGER)
    public Collection<FooBar> getFooBars() {
        return fooBars;
    }

    public void setFooBars(Collection<FooBar> fooBars) {
        this.fooBars = fooBars;
    }

    // use this to maintain bidirectional integrity
    public void addBar(Bar bar) {
        FooBar fooBar = new FooBar(bar, this);

        fooBars.add(fooBar);
        bar.getFooBars().add(fooBar);
    }

    // use this to maintain bidirectional integrity
    public void removeBar(Bar bar){
        // I do not want to disclose the code for findFooBarFor(). It works 100%, and is not reloading data from DB
        FooBar fooBar = findFooBarFor(bar, this); 

        fooBars.remove(fooBar);
        bar.getFooBars().remove(fooBar);
    }

}

public class Bar {

    private Collection<FooBar> fooBars = new HashSet<>();

    // constructor omitted for brevity

    @OneToMany(fetch = FetchType.EAGER, mappedBy = "bar", cascade = CascadeType.ALL)
    public Collection<FooBar> getFooBars() {
        return fooBars;
    }

    public void setFooBars(Collection<FooBar> fooBars) {
        this.fooBars = fooBars;
    }
}

public class FooBar {

    private FooBarId id; // embeddable class with foo and bar (only ids)
    private Foo foo;
    private Bar bar;

    // this is why I had to use this helper class (FooBar), 
    // else I could have made a direct @ManyToMany between Foo and Bar
    private Double additionalInformation; 

    public FooBar(Foo foo, Bar bar){
        this.foo = foo;
        this.bar = bar;
        this.additionalInformation = .... // not important
        this.id = new FooBarId(foo.getId(), bar.getId());
    }

    @EmbeddedId
    public FooBarId getId(){
        return id;
    }

    public void setId(FooBarId id){
        this.id = id;
    }

    @ManyToOne
    @MapsId("foo")
    @JoinColumn(name = "fooid", referencedColumnName = "id")
    public Foo getFoo() {
        return foo;
    }

    public void setFoo(Foo foo) {
        this.foo = foo;
    }

    @ManyToOne
    @MapsId("bar")
    @JoinColumn(name = "barid", referencedColumnName = "id")
    public Bar getBar() {
        return bar;
    }

    public void setBar(Bar bar) {
        this.bar = bar;
    }

    // getter, setter for additionalInformation omitted for brevity
}

Ответы [ 3 ]

0 голосов
/ 25 сентября 2018

Я попробовал это из примера кода.С парой «набросков» это воспроизвело ошибку.

Решение оказалось таким же простым, как добавление упомянутого вами orphanRemoval = true.На Foo.getFooBars():

@OneToMany(cascade = CascadeType.ALL, mappedBy = "foo", fetch = FetchType.EAGER, orphanRemoval = true)
public Collection<FooBar> getFooBars() {
    return fooBars;
}

Казалось, проще всего опубликовать это воспроизведение до GitHub - надеюсь, есть еще одна тонкая разница или что-то, что я пропустил там.

Он основан на Spring Boot и базе данных H2 в памяти, поэтому должен работать без какой-либо другой среды - просто попробуйте mvn clean test, если сомневаетесь.

Класс FooRepositoryTest имеет тестовый набор.У него есть проверка на удаление ссылки FooBar, или может быть проще прочитать SQL, который регистрируется.


Редактировать

Это скриншот, упомянутый в комментарии ниже: deleteOrphans() breakpoint

0 голосов
/ 25 сентября 2018

Я протестировал ваш сценарий и выполнил следующие три модификации, чтобы он заработал:

  1. Добавлено orphanRemoval = true к обоим @OneToMany getFooBars () методы из Foo и Bar.Для вашего конкретного сценария было бы достаточно добавить его в Foo, но вы, вероятно, захотите того же эффекта, когда вы удаляете foo и из бара.
  2. Включен вызов foo.removeBar (bar) внутри аннотированного методас пружиной @ Транзакционный .Вы можете поместить этот метод в новый класс @ Service FooService .
    Причина: orphanRemoval для работы требуется активный транзакционный сеанс.
  3. Удален вызов barRepository.save (bar) после вызова foo.removeBar (bar) .
    Теперь это избыточно, потому что внутри сеанса транзакций изменения сохраняются автоматически.
0 голосов
/ 21 сентября 2018

Сохранение Java 2.1.Глава 3.2.3

Операция удаления

• Если X - новый объект, он игнорируется операцией удаления .Однако операция удаления каскадно относится к объектам, на которые ссылается X, если связь между X и этими другими объектами аннотируется значением элемента аннотации cascade = REMOVE или cascade = ALL.

• Если X является управляемым объектом, операция удаления приводит к его удалению.Операция удаления каскадно относится к объектам, на которые ссылается X, если отношения между X и этими другими объектами аннотируются значением элемента аннотации cascade = REMOVE или cascade = ALL.

Убедитесь, что вы уже используете операциюpersist для вас, сущности Foo (или FooBar или Bar).

...