JPA OneToMany не удаляет ребенка

158

У меня проблема с простым @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 из набора childs, он не будет удален из базы данных. Я попытался аннулировать child.parentссылку, но это тоже не сработало.

Сущности используются в веб-приложении, удаление происходит как часть Ajax-запроса. У меня нет списка удаленных дочерних элементов при нажатии кнопки сохранения, поэтому я не могу удалить их неявно.

Берт
источник

Ответы:

253

Поведение JPA правильное (имеется в виду согласно спецификации ): объекты не удаляются просто потому, что вы удалили их из коллекции OneToMany. Существуют специфичные для вендора расширения, которые делают это, но нативный JPA не учитывает это.

Отчасти это происходит потому, что JPA на самом деле не знает, следует ли удалять что-то, удаленное из коллекции. В терминах объектного моделирования это разница между составом и «агрегацией» *.

В композиции дочерняя сущность не существует без родителя. Классический пример - между Домом и Комнатой. Удалить дом и комнаты тоже.

Агрегация - более слабая ассоциация, типичная для курса и студента. Удалите курс, и студент все еще существует (возможно, в других курсах).

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

Я знаю о:

Клетус
источник
спасибо хорошее объяснение. так, как я боялся. (Я сделал поиск / чтение, прежде чем я спросил, просто хотел сохранить). как-то я начинаю сожалеть о решении использовать JPA API и сразу Hibernate .. Я попробую указатель Чандра Патни и использую каскадный тип delete_orphan hibernate.
Берт
У меня есть похожий вопрос по этому поводу. Будь добр, посмотри сюда этот пост, пожалуйста. stackoverflow.com/questions/4569857/…
Тханг Фам
78
В JPA 2.0 теперь вы можете использовать опцию orphanRemoval = true
itsadok
2
Отличное начальное объяснение и полезные советы по удалению сирот. Не знал, что JPA не учитывает этот тип удаления. Нюансы между тем, что я знаю о Hibernate, и тем, что на самом деле делает JPA, могут сводить с ума.
СМА
Приятно объяснено, разоблачая различия между Композицией и Агрегацией!
Фелипе Леан
73

В дополнение к ответу Cletus, JPA 2.0 , последний с декабря 2010 года, вводит orphanRemovalатрибут @OneToManyаннотаций. Для более подробной информации смотрите эту запись в блоге .

Обратите внимание, что, поскольку спецификация является относительно новой, не все поставщики JPA 1 имеют окончательную реализацию JPA 2. Например, выпуск Hibernate 3.5.0-Beta-2 пока не поддерживает этот атрибут.

Луи Жакомет
источник
запись в блоге - ссылка не работает.
Штеффи
1
И магия машины обратного пути спасает нас: web.archive.org/web/20120225040254/http://javablog.co.uk/2009/…
Луи Жакомет
43

Вы можете попробовать это:

@OneToOne(orphanRemoval=true) or @OneToMany(orphanRemoval=true),

Масиэль Бомбонато
источник
2
Спасибо. Но вопрос вернулся из JPA 1 раз. И эта опция не была доступна назад, чем.
Bert
9
Тем не менее, это полезно знать тем из нас, кто ищет решение для этого во времена JPA2 :)
alex440 15.10.15
20

Как было объяснено, с JPA невозможно сделать то, что я хочу, поэтому я применил аннотацию hibernate.cascade, при этом соответствующий код в классе Parent теперь выглядит следующим образом:

@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>();

Я не мог просто использовать «ALL», так как это также удалило бы родителя.

Берт
источник
4

Здесь каскад в контексте удаления означает, что дочерние элементы удаляются, если вы удаляете родительский элемент. Не ассоциация. Если вы используете Hibernate в качестве вашего JPA-провайдера, вы можете сделать это, используя специальный каскад hibernate .

Чандра Патни
источник
4

Вы можете попробовать это:

@OneToOne(cascade = CascadeType.REFRESH) 

или

@OneToMany(cascade = CascadeType.REFRESH)
Амит Горпаде
источник
3
@Entity 
class Employee {
     @OneToOne(orphanRemoval=true)
     private Address address;
}

Смотрите здесь .

Чанг
источник