Подтвердить что ты не робот

JPA: УДАЛИТЬ ГДЕ НЕ удаляет детей и выбрасывает исключение

Я пытаюсь удалить большое количество строк из MOTHER благодаря запросу JPQL.

Класс MOTHER определяется следующим образом:

@Entity
@Table(name = "MOTHER")
public class Mother implements Serializable {

    @OneToMany(cascade = CascadeType.ALL, mappedBy = "mother", 
               orphanRemoval = true)
    private List<Child> children;    
}

@Entity
@Table(name = "CHILD")
public class Child  implements Serializable {

    @ManyToOne
    @JoinColumn(name = "MOTHER_ID")
    private Mother mother;    
}

Как вы можете видеть, класс MOTHER имеет "дети" и при выполнении следующего запроса:

String deleteQuery = "DELETE FROM MOTHER WHERE some_condition";
entityManager.createQuery(deleteQuery).executeUpdate();

генерируется исключение:

ERROR - ORA-02292: integrity constraint <constraint name> violated - 
                   child record found

Конечно, я мог бы сначала выбрать все объекты, которые я хочу удалить, и загрузить их в список, прежде чем повторять его, чтобы удалить весь восстановленный объект, но производительность такого решения просто ужасна!

Итак, есть ли способ использовать предыдущее сопоставление, чтобы удалить все объекты MOTHER и все связанные с ними объекты Child, и не записывая сначала запросы для all дети?

4b9b3361

Ответ 1

DELETE (и INSERT) не каскадируются через отношения в запросе JPQL. Это четко указано в спецификации:

Операция удаления применяется только к объектам указанного класса и его подклассов. Он не каскадируется для связанных объектов.

Удачно сохраняются и удаляются с помощью диспетчера сущностей (при наличии определенного каскадного атрибута).

Что вы можете сделать:

  • выберите все экземпляры материнской сущности, которые необходимо удалить.
  • для каждого из них вызывается EntityManager.remove().

Код выглядит примерно так:

String selectQuery = "SELECT m FROM Mother m WHERE some_condition";  
List<Mother> mothersToRemove = entityManager.createQuery(selectQuery).getResultList();  
for (Mother m: mothersToRemove) {  
    em.remove(m);
}

Ответ 2

Пробовали ли вы использовать session.delete() или эквивалент EntityManager.remove()?

Когда вы используете инструкцию HQL delete для запроса, вы можете обходить каскадный механизм Hibernate. Взгляните на этот вопрос JIRA: HHH-368

Вы, возможно, сможете это сделать:

Mother mother = session.load(Mother.class, id);
// If it is a lazy association, 
//it might be necessary to load it in order to cascade properly
mother.getChildren(); 
session.delete(mother);

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

Ответ 3

Это связано и может предложить решение, если вы используете Hibernate.

JPA CascadeType.ALL не удаляет сирот

ИЗМЕНИТЬ

Поскольку Oracle - это тот, который дает вам ошибку, вы могли бы использовать Oracle cascade delete, чтобы обойти это. Однако это может иметь непредсказуемые результаты: поскольку JPA не понимает, что вы удаляете другие записи, эти объекты могут оставаться в кеше и использоваться, даже если они были удалены. Это применимо только в том случае, если реализация JPA, используемая вами, имеет кэш и настроена на ее использование.

Вот информация об использовании каскадного удаления в Oracle: http://www.techonthenet.com/oracle/foreign_keys/foreign_delete.php

Ответ 4

Должен сказать, что я не уверен, что если "delete" в запросе не удалит все связанные с ним объекты onomomany в вашем случае, как говорит "MikKo Maunu". Я бы сказал, что так будет. Проблема (извините за то, что вы не пытаетесь это сделать), что JPA/Hibernate будет выполнять только "реальное sql-удаление", и пока эти экземпляры "Мать и ребенок" не управляются в этот момент, у него нет способа узнать, какой ребенок экземпляры для удаления тоже. orphanRemoval - отличная помощь, но не в этом случае. Я бы

1) попробуйте добавить 'fetch = FetchType.EAGER' в отношение onetomany (это может быть и проблема с производительностью)

2) если 1) не работает, не делайте все выборки Mother/Child, чтобы сделать все ясно для уровня JPA, и просто запустите запрос до того, который вы используете (в той же транзакции, но я не уверен, что вам не нужно запускать "em.flush" между ними)

DELETE FROM Child c WHERE c.mother <the condition>

(Удаляет часто неприятность с JPA/Hibernate и один пример, который я использую для денонсации использования ORM, который по существу является добавленным слоем в приложениях, чтобы сделать вещи "более легкими". Только хорошо, что ORM проблемы/ошибки обычно обнаруживаются на этапе разработки. Мои деньги всегда на MyBatis, который, по моему мнению, намного чище.)

UPDATE:

Mikko Maunu прав, объемное удаление в JPQL не каскадируется. Использование двух запросов, как я предложил, является хорошим, хотя.

Сложно сказать, что этот контекст persistence (все сущности, управляемые EntityManager) не синхронизированы с тем, что делает массовое удаление, поэтому он (оба запроса в том случае, если я предлагаю) должен запускаться в отдельной транзакции.

ОБНОВЛЕНИЕ 2: Если вы используете ручной удаление вместо массового удаления, многие поставщики JPA и Hibernate также предоставляют метод removeAll (...) или что-то подобное (не API) в своих реализациях EntityManager. Он проще использовать и может быть более эффективным в отношении производительности.

В частности. OpenJPA вам нужно всего лишь направить EM на OpenJPAEntityManager, лучше всего на OpenJPAPersistence.cast(em).removeAll(...)

Ответ 5

Вы можете ретранслировать на РСУБД, чтобы удалить те Mother с помощью ограничения внешнего ключа. Это предполагает, что вы генерируете свой DDL из объектов:

@Entity
@Table(name = "CHILD")
public class Child  implements Serializable {

    @ManyToOne
    @JoinColumn(name = "MOTHER_ID", foreignKey = @ForeignKey(foreignKeyDefinition =
        "FOREIGN KEY(MOTHER_ID) REFERENCES MOTHER(ID) ON DELETE CASCADE",
        value = ConstraintMode.CONSTRAINT))
    private Mother mother;    
}