JPA-изменение ссылки на существующую (но обособленную) сущность приводит к вставке дубликата


У меня есть две сущности с однонаправленным отношением "многие к одному":

  • У многих Barесть один Foo

Когда я пытаюсь обновить управляемый экземпляр Bar, изменив его Foo на существующий отсоединенный экземпляр Foo, сброс в БД завершается неудачей.

Я использую фасады (FooFacade & BarFacade) для создания и изменения сущностей. Вот мой тестовый код:

public void test() {
    FooFacade fooFacade = (FooFacade) lookupEJB(FooFacade.class);
    BarFacade barFacade = (BarFacade) lookupEJB(BarFacade.class);

    Foo originalFoo = fooFacade.createFoo();    // returned entity is detatched
    Foo newFoo = fooFacade.createFoo();         // returned entity is detatched

    Bar bar = barFacade.createBar(originalFoo); // returned entity is detatched

    barFacade.changeFoo(bar.getId(), newFoo);
}

Этот код вызовет следующую ошибку SQL:

Заявление было прервано потому что это вызвало бы дубликат ключа значение в ограничении уникального или первичного ключа или уникальном индексе, идентифицированном по SQL130321134048610' определен 'фу'.

Eclipselink пытается вставить newFoo, когда смывает изменение bar, хотя оно уже существует!

Однако тот факт, что экземпляр newFoo отсоединен, не должен иметь никакого значения - см. соответствующую часть спецификации персистентности EJB (раздел 3.2.3, стр. 50) в жирным шрифтом ниже:

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

  • Если X является управляемой сущностью, она синхронизируется с базой данных.
    • для всех сущностей Y, на которые ссылается отношение из X, если отношение к Y было аннотировано значением элемента каскада cascade=PERSIST или cascade= ALL, операция persist применяется к Y.
    • для любой сущности Y, на которую ссылается отношение из X, где отношение к Y не было аннотировано элементом каскада стоимость каскада=сохраняться или каскад= все:
      • если Y является новым или удаленным, то операция flush (и транзакция откатывается) или Операция flush вызовут исключение IllegalStateException. сделку совершить не удастся.
      • , если y-это отдельно стоящее, семантика зависит от собственности на отношения. Если x владеет отношением, любые изменения в отношения синхронизируются с базой данных ; в противном случае, если y принадлежит отношения, поведение не определены.
  • Если X является удаленной сущностью, то она удаляется из базы данных. Никакие каскадные варианты не имеют значения.
Может ли кто-нибудь объяснить, почему это не работает? Я не могу найти существующий отчет об ошибке eclipselink. У меня есть некоторый код ниже, и технологии таковы:
  • EclipseLink (тестирование с использованием встроенного контейнера Glassfish EJB)
  • дерби (для тестирование этой проблемы - в производстве я использую MS SQL, и он также терпит неудачу)

Спасибо!

Пожалуйста, обратите внимание: я понимаю, что я мог бы сделать эту работу по

  1. слияние отсоединенного Foo в тот же контекст сохранения, который используется при обновлении bar, а затем
  2. установка объединенного экземпляра foo на bar.
Однако это не тот подход, который я хотел бы использовать в своей работе. приложение.

обновление 1: я включил часть спецификации JPA, которая предполагает, что это должно работать

обновление 2: упрощенный пример сценария

Сущности

@Entity
@TableGenerator(name="test_generator", table="SEQUENCE", pkColumnName="SEQ_NAME", valueColumnName="SEQ_VALUE", pkColumnValue="TEST_SEQUENCE")
public class Foo {

    @Id
    @GeneratedValue(strategy=GenerationType.TABLE, generator="test_generator")
    private int id;

    public int getId() {return this.id;}
    public void setId(int id) {this.id = id;}

}

И

@Entity
public class Bar {

    @Id
    @GeneratedValue(strategy=GenerationType.TABLE, generator="test_generator")
    private int id;

    @ManyToOne
    private Foo foo;

    public int getId() {return this.id;}
    public void setId(int id) {this.id = id;}

    public Foo getFoo() {return this.foo;}
    public void setFoo(Foo foo) {this.foo = foo;}

}

Фасады

@Singleton
@LocalBean
public class FooFacade {

    @PersistenceContext(unitName="CurriculumManagementSystem")
    private EntityManager em;

    public Foo createFoo() {
        Foo newFoo = new Foo();

        em.persist(newFoo);

        return newFoo;
    }
}

И

@Singleton
@LocalBean
public class BarFacade {

    @PersistenceContext(unitName="CurriculumManagementSystem")
    private EntityManager em;

    public Bar createBar(Foo parent) {
        Bar newBar = new Bar();

        newBar.setFoo(parent);

        em.persist(newBar);

        return newBar;
    }

    public void changeFoo(int barID, Foo detachedFoo) {
        Bar bar = (Bar) em.find(Bar.class, barID);

        bar.setFoo(detachedFoo);

        // when this method exits, the transaction completes
        // and any changes are flushed to the DB.

        // instead of only updating the Bar table by changing the
        // foreign key reference to the detachedFoo's ID, elipselink
        // tries to insert the detachedFoo as a new row in the
        // Foo table!
    }
2 2

2 ответа:

Эта проблема вызвана ошибкой в EclipseLink:

Любые изменения, которые ссылаются на отсоединенный объект, приведут к вставке отсоединенного объекта

Это было исправлено в версии 2.4.0. Об этом сообщалось в версии 1.1.3.

На момент написания статьи версия EclipseLink, распространяемая с последней версией Glassfish (3.1.2), составляет 2.3.2, поэтому мне придется либо дождаться будущего выпуска Glassfish, либо предоставить более новые библиотеки EclipseLink с моим приложение.

В настоящее время я буду ждать и использовать обходной путь слияния, описанный в моем первоначальном сообщении. Спасибо всем, кто пытался помочь мне в этом вопросе! :)

Звучит так, как будто вы разрушаете свои объекты, смешивая управляемые и отделенные объекты. Вы не должны смешивать эти два понятия.

Если вы хотите изменить ссылку на объект, вы должны выполнить функцию find() или getReference() для новой ссылки в том же контексте сохранения (EntityManager/transaction).

Вам также необходимо правильно поддерживать двунаправленные отношения.