Hibernate многие-ко-многим с каскадной проблемой join-class

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

я создал Foo и создал несколько баров (сохраненных в DB). Когда я затем добавляю один из баров в 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-это не удалено из БД. С отладкой я увидел, что foo.removeBar(bar); действительно удалил двунаправленно. Без исключений.

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


Что Я попробовали:

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

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


Edit: я подозреваю, что должен быть что - то в моем коде или модели, что связывается с моим 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 ответов


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

разрешение оказалось таким же простым, как добавление orphanRemoval = true вы хоть упомянули. On 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


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

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

Сохраняемость Java 2.1. Глава 3.2.3

операции удалить

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

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

убедитесь, что вы уже используете operation persist для лиц Foo(или FooBar или Bar).