JPA — Удаление элементов двунаправленной связи

#orm #jpa #relationship #jpa-2.0 #bidirectional

#orm #jpa #взаимосвязь #jpa-2.0 #двунаправленный

Вопрос:

Почему я могу удалить элементы двунаправленной связи, хотя в контексте сохранения управляется только одна сторона связи (пример I)? Когда у меня есть однонаправленная связь, которая не работает (см. Пример II). Почему?

Сущности:

 @Entity
Class User {
    ...
    @OneToMany(mappedBy = "user")
    private List<Process&&t; processes;

    @OneToOne // Unidirectional
    private C c;
    ...

    @PreRemove
    private void preRemove() {
        for (Process p : processes) {
            p.internalSetUser(null);
        }
    }
   ...
}

@Entity
Class Process {
    ...
    @ManyToOne
    private User user;
    ...

    @PreRemove
    protected void preRemove() {
        if (this.user != null) {
            user.internalRemoveProcess(this);
        }
    }
   ...
}

@Entity
Class C {

 }
  

Пример I:

 // Create User u1 with Processes p1, p2

tx.start();
// Only u1 is man&ed in persistence context and no process
userFacade.delete(u1); // There followin& is called: &&t;&&t; em.remove(em.mer&e(u1)); // Works
tx.commit();
  

Пример II:

 // Create User u and Object C c, establish their relation.

tx.start();
cFacade.remove(c); //&&t;&&t;MySQLInte&rityConstraintViolationException,forei&n key constraint fails
ty.commit();
  

В первом примере я использую эти внутренние методы для установки в каждом случае другой стороны связи, но эта другая сторона, я думаю, не управляется в контексте сохранения ?! Когда я изменяю процесс пользователя и сохраняю пользователя, процесс не обновляется, если я не использую cascade.СЛИЯНИЕ или если оба загружены в транзакцию и поэтому управляются с ПК. Итак, почему удаление работает?

Ответ №1:

В примере II, я полагаю, вам пришлось бы вызвать user.setC(null) перед удалением c .

В примере I, вот мое понимание. Сначала выполняется слияние, u1 поэтому u1' на компьютер загружается a, а состояние u1 копируется в u1' (и это все, поскольку вы не выполняете каскадирование MERGE ), которое затем возвращается. Затем вы вызываете remove (вкл u1' ), preRemove вызывается и изменяется p1' и p2' . Таким образом, они загрязнены и будут соответствующим образом обновлены при сбросе (установка FK в NULL значение), в то время как u1' они будут удалены. И все работает.

На всякий случай, вот семантика операции слияния из спецификации JPA 2.0:

3.2.7.1 Объединение отделенного состояния объекта

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

Семантика операции слияния, применяемой к объекту X, заключается в следующем:

  • Если X является обособленной сущностью, состояние X X' копируется в уже существующий экземпляр управляемой сущности X' с тем же идентификатором или создается новая управляемая копия X из, , создается.
  • Если X это новый экземпляр объекта, то X' создается новый экземпляр управляемого объекта, а состояние X копируется в новый экземпляр управляемого объекта X' .
  • Если X это удаленный экземпляр объекта, то в результате операции слияния будет создан экземпляр Ille&alAr&umentException (или произойдет сбой фиксации транзакции).
  • Если X это управляемая сущность, она игнорируется операцией слияния, однако операция слияния каскадируется на сущности, на которые ссылаются отношения из X , если эти отношения были аннотированы значением каскадного элемента cascade=MERGE или cascade=ALL аннотацией.
  • Для всех объектов, на которые Y ссылаются отношения из X , имеющих значение каскадного элемента cascade=MERGE или cascade=ALL , Y рекурсивно объединяется как Y' . Для всех таких, на которые Y ссылается X , X' устанавливается значение reference Y' . (Обратите внимание, что если X управляется, то X это тот же объект, что и X' .)
  • Если X объект объединен с X' со ссылкой на другой объект Y , где cascade=MERGE или cascade=ALL не указано, то переход по той же ассоциации из X' приводит к ссылке на управляемый объект Y' с тем же постоянным идентификатором, что и Y .

Поставщик сохраняемости не должен объединять поля, помеченные как ОТЛОЖЕННЫЕ, которые не были извлечены: он должен игнорировать такие поля при объединении.

Любые Version столбцы, используемые объектом, должны быть проверены реализацией среды выполнения сохранения во время операции слияния и / или во время очистки или фиксации. При отсутствии Version столбцов среда выполнения поставщика сохраняемости не выполняет дополнительную проверку версии во время операции слияния.

Ссылка

  • Спецификация JPA 2.0
    • 3.2.7.1 Объединение отделенного состояния объекта

Комментарии:

1. Спасибо Pascal! Я думаю, что в обоих случаях вы правы. Например, это последний пункт приведенной выше спецификации.

2. Если я использую для примера, я не объединяю, а делаю следующее: «Пользователь u1Mana&ed = em.find(u1.&etId()); em.remove(u1Mana&ed);» Тогда это тоже работает. Это означает, что операция поиска также приводит к управляемым p1′ и p2′?

3. @Michael processes Ленивы, но я думаю, что p1 и p2 загружается в ваш PreRemove (и таким образом становится действительно управляемым).

Ответ №2:

Это ожидаемое поведение:

Поскольку связь является двунаправленной, так как приложение обновляет одну сторону связи, другая сторона также должна обновляться и синхронизироваться. В JPA, как и в Java в целом за поддержание связей отвечает приложение или объектная модель. Если ваше приложение добавляет к одной стороне связи, то оно должно добавить и к другой стороне.

Это можно решить с помощью методов add или set в объектной модели, которые обрабатывают обе стороны связей, поэтому коду приложения не нужно беспокоиться об этом. Для этого есть два способа: вы можете либо добавить код обслуживания связи только на одну сторону связи и использовать установщик только с одной стороны (например, сделать другую сторону защищенной), либо добавить его на обе стороны и гарантировать, что вы избежите бесконечного цикла.

Например:

 public class Employee {
    private List phones;
    ...
    public void addPhone(Phone phone) {
        this.phones.add(phone);
        if (phone.&etOwner() != this) {
            phone.setOwner(this);
        }
    }
    ...
}
  

Источник: OneToMany#Getters_and_Setters