JPA OneToMany не удаляет дочерний элемент
Вопрос
У меня проблема с простым @OneToMany
сопоставление между родительским и дочерним объектом.Все работает хорошо, только дочерние записи не удаляются, когда я удаляю их из коллекции.
Родитель:
@Entity
public class Parent {
@Id
@Column(name = "ID")
private Long id;
@OneToMany(cascade = {CascadeType.ALL}, mappedBy = "parent")
private Set<Child> childs = new HashSet<Child>();
...
}
Ребенок:
@Entity
public class Child {
@Id
@Column(name = "ID")
private Long id;
@ManyToOne(cascade = CascadeType.ALL)
@JoinColumn(name="PARENTID", nullable = false)
private Parent parent;
...
}
Если я теперь удалю дочерний элемент из набора дочерних элементов, он не будет удален из базы данных.Я попробовал обнулить child.parent
ссылку, но это тоже не сработало.
Сущности используются в веб-приложении, удаление происходит как часть запроса Ajax.У меня нет списка удаленных дочерних элементов при нажатии кнопки сохранения, поэтому я не могу удалить их неявно.
Решение
Поведение JPA правильное (то есть согласно спецификации):объекты не удаляются просто потому, что вы удалили их из коллекции OneToMany.Существуют расширения, специфичные для конкретного поставщика, которые делают это, но собственный JPA этого не поддерживает.
Частично это связано с тем, что JPA на самом деле не знает, следует ли удалять что-то удаленное из коллекции.С точки зрения объектного моделирования, в этом заключается разница между состав и «агрегация*.
В состав, дочерняя сущность не существует без родителя.Классический пример – между Домом и Комнатой.Удалите Дом и Комнаты тоже.
Агрегация — это более свободный вид ассоциации, типичный пример которого — «Курс» и «Студент».Удалите курс, и студент все еще существует (возможно, в других курсах).
Поэтому вам нужно либо использовать расширения, специфичные для поставщика, чтобы принудительно выполнить такое поведение (если доступно), либо явно удалить дочерний элемент И удалить его из родительской коллекции.
Я знаю:
- Спящий режим:каскадное удаление_orphan.Видеть 10.11.Транзитивное постоянство;и
- ЗатмениеСсылка:называет это «частной собственностью».Видеть Как использовать аннотацию @PrivateOwned.
Другие советы
В дополнение к ответу Клетуса, JPA 2.0, окончательный с декабря 2010 года, вводит orphanRemoval
атрибут включен @OneToMany
аннотации.Более подробную информацию см. здесь запись в блоге.
Обратите внимание: поскольку спецификация относительно новая, не все поставщики JPA 1 имеют окончательную реализацию JPA 2.Например, Выпуск Hibernate 3.5.0-Beta-2 еще не поддерживает этот атрибут.
Вы можете попробовать это:
@OneToOne(orphanRemoval=true) or @OneToMany(orphanRemoval=true)
.
Как уже объяснялось, с JPA невозможно делать то, что я хочу, поэтому я использовал аннотацию hibernate.cascade, благодаря чему соответствующий код в родительском классе теперь выглядит следующим образом:
@OneToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH}, mappedBy = "parent")
@Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE,
org.hibernate.annotations.CascadeType.DELETE,
org.hibernate.annotations.CascadeType.MERGE,
org.hibernate.annotations.CascadeType.PERSIST,
org.hibernate.annotations.CascadeType.DELETE_ORPHAN})
private Set<Child> childs = new HashSet<Child>();
Я не мог просто использовать «ВСЕ», так как это также удалило бы родителя.
Здесь каскад в контексте удаления означает, что дочерние элементы удаляются, если вы удаляете родителя.Не ассоциация.Если вы используете Hibernate в качестве поставщика JPA, вы можете сделать это, используя конкретный каскад спящего режима.
Вы можете попробовать это:
@OneToOne(cascade = CascadeType.REFRESH)
или
@OneToMany(cascade = CascadeType.REFRESH)
@Entity
class Employee {
@OneToOne(orphanRemoval=true)
private Address address;
}
Видеть здесь.