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, который регистрируется.
редактировать
я протестировал ваш сценарий и сделал следующие три модификации, чтобы он работал:
- добавил orphanRemoval=true и @OneToMany getFooBars() методы из Foo и Bar. Для вашего конкретного сценария достаточно добавить его в Foo, но вы, вероятно, хотите тот же эффект, когда вы удаляете foo из бара.
- вложил foo.вызов removeBar (bar) внутри метода, аннотированного Spring @Transactional. Вы можете поместить этот метод в новый @Service FooService класса.
причина: orphanRemoval требует активного сеанса транзакций для работы. - удалены вызов 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
).