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) в жирным шрифтом ниже:
Может ли кто-нибудь объяснить, почему это не работает? Я не могу найти существующий отчет об ошибке eclipselink. У меня есть некоторый код ниже, и технологии таковы:Семантика операции flush, примененная к сущности X, выглядит следующим образом: образом:
- Если X является управляемой сущностью, она синхронизируется с базой данных.
- для всех сущностей Y, на которые ссылается отношение из X, если отношение к Y было аннотировано значением элемента каскада cascade=PERSIST или cascade= ALL, операция persist применяется к Y.
- для любой сущности Y, на которую ссылается отношение из X, где отношение к Y не было аннотировано элементом каскада стоимость каскада=сохраняться или каскад= все:
- если Y является новым или удаленным, то операция flush (и транзакция откатывается) или Операция flush вызовут исключение IllegalStateException. сделку совершить не удастся.
- , если y-это отдельно стоящее, семантика зависит от собственности на отношения. Если x владеет отношением, любые изменения в отношения синхронизируются с базой данных ; в противном случае, если y принадлежит отношения, поведение не определены.
- Если X является удаленной сущностью, то она удаляется из базы данных. Никакие каскадные варианты не имеют значения.
- EclipseLink (тестирование с использованием встроенного контейнера Glassfish EJB)
- дерби (для тестирование этой проблемы - в производстве я использую MS SQL, и он также терпит неудачу)
Спасибо!
Пожалуйста, обратите внимание: я понимаю, что я мог бы сделать эту работу по
Однако это не тот подход, который я хотел бы использовать в своей работе. приложение.
- слияние отсоединенного
Foo
в тот же контекст сохранения, который используется при обновленииbar
, а затем- установка объединенного экземпляра
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 ответа:
Эта проблема вызвана ошибкой в EclipseLink:
Это было исправлено в версии 2.4.0. Об этом сообщалось в версии 1.1.3.
На момент написания статьи версия EclipseLink, распространяемая с последней версией Glassfish (3.1.2), составляет 2.3.2, поэтому мне придется либо дождаться будущего выпуска Glassfish, либо предоставить более новые библиотеки EclipseLink с моим приложение.
В настоящее время я буду ждать и использовать обходной путь слияния, описанный в моем первоначальном сообщении. Спасибо всем, кто пытался помочь мне в этом вопросе! :)
Звучит так, как будто вы разрушаете свои объекты, смешивая управляемые и отделенные объекты. Вы не должны смешивать эти два понятия.
Если вы хотите изменить ссылку на объект, вы должны выполнить функцию find() или getReference() для новой ссылки в том же контексте сохранения (EntityManager/transaction).
Вам также необходимо правильно поддерживать двунаправленные отношения.