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

Сохранение каскада JPA и ссылки на отдельные объекты бросают PersistentObjectException. Зачем?

У меня есть сущность Foo, которая ссылается на строку сущности:

@Entity
public class Foo {

    @OneToOne(cascade = {PERSIST, MERGE, REFRESH}, fetch = EAGER)
    public Bar getBar() {
        return bar;
    }
}

Когда я сохраняю новый Foo, он может получить ссылку на новую панель или существующую панель. Когда он получает существующий бар, который отключается, мой поставщик JPA (Hibernate) выдает следующее исключение:

Caused by: org.hibernate.PersistentObjectException: detached entity passed to persist: com.example.Bar
 at org.hibernate.event.def.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:102)
 at org.hibernate.impl.SessionImpl.firePersist(SessionImpl.java:636)
 at org.hibernate.impl.SessionImpl.persist(SessionImpl.java:628)
 at org.hibernate.engine.EJB3CascadingAction$1.cascade(EJB3CascadingAction.java:28)
 at org.hibernate.engine.Cascade.cascadeToOne(Cascade.java:291)
 at org.hibernate.engine.Cascade.cascadeAssociation(Cascade.java:239)
 at org.hibernate.engine.Cascade.cascadeProperty(Cascade.java:192)
 at org.hibernate.engine.Cascade.cascade(Cascade.java:153)
 at org.hibernate.event.def.AbstractSaveEventListener.cascadeBeforeSave(AbstractSaveEventListener.java:454)
 at org.hibernate.event.def.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:288)
 at org.hibernate.event.def.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:204)
 at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:130)
 at org.hibernate.ejb.event.EJB3PersistEventListener.saveWithGeneratedId(EJB3PersistEventListener.java:49)
 at org.hibernate.event.def.DefaultPersistEventListener.entityIsTransient(DefaultPersistEventListener.java:154)
 at org.hibernate.event.def.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:110)
 at org.hibernate.event.def.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:61)
 at org.hibernate.impl.SessionImpl.firePersist(SessionImpl.java:645)
 at org.hibernate.impl.SessionImpl.persist(SessionImpl.java:619)
 at org.hibernate.impl.SessionImpl.persist(SessionImpl.java:623)
 at org.hibernate.ejb.AbstractEntityManagerImpl.persist(AbstractEntityManagerImpl.java:220)
 ... 112 more

Когда я либо убеждаюсь, что ссылка на Bar управляется (присоединена), либо когда я опускаю каскад PERSIST в отношении, все работает хорошо.

Однако ни одно решение не удовлетворяет на 100%. Если я удалю каскадный упор, я, очевидно, больше не могу упорствовать в Foo со ссылкой на новый Bar. Сопоставление ссылки на Bar managed требует такого кода до этого:

if (foo.getBar().getID() != null && !entityManager.contains(foo.getBar())) {
    foo.setBar(entityManager.merge(foo.getUBar()));
}
entityManager.persist(foo);

Для одного бара это может показаться не очень сложным, но если мне придется учитывать все свойства, то в итоге я получаю довольно ужасный код, который, по-видимому, победит причину использования ORM в первую очередь, Я мог бы еще раз сохранить мой объектный граф вручную, используя JDBC снова.

При предоставлении существующей ссылки на баре единственное, что нужно сделать JPA, это взять ее идентификатор и вставить его в столбец таблицы, содержащий Foo. Он делает именно это, когда Bar прикреплен, но выдает исключение, когда Bar отсоединен.

Мой вопрос: зачем он нужен? Разумеется, его идентификатор не изменится, когда экземпляр Bar перейдет из отсоединенного состояния в прикрепленное, и этот идентификатор, по-видимому, является единственным, что нужно здесь.

Возможно, это ошибка в Hibernate или я что-то не хватает?

4b9b3361

Ответ 1

Вы можете использовать merge() вместо persist() в этом случае:

foo = entityManager.merge(foo); 

При применении к новому экземпляру merge() делает его постоянным (фактически - возвращает постоянный экземпляр с тем же состоянием) и объединяет каскадные ссылки, когда вы пытаетесь сделать вручную.

Ответ 2

Если я правильно понимаю, вам просто нужна ссылка Bar, чтобы новый Foo имел значение внешнего ключа (до существующего Bar) при сохранении. Существует метод JPA для EntityManager, называемый getReference(), который может быть вам полезен для этого случая. Метод getReference() похож на find(), за исключением того, что он не захочет возвращать управляемый экземпляр (Bar), если только он не будет кэширован в контексте персистентности. Он вернет прокси-объект, который удовлетворит ваши потребности внешнего ключа, чтобы сохранить объект Foo. Я не уверен, что это то решение, на которое вы надеялись, но попробуйте и посмотрите, работает ли это для вас.

Я также заметил из вашего кода, что вы используете доступ к стилю типа "свойство" вместо "стиля" в стиле поля, аннотируя ваш метод getter (для отношения Bar). Любая причина для этого? Он рекомендовал, чтобы вы комментировали членов, а не геттеров по соображениям производительности. Предполагается, что поставщик JPA более эффективен для доступа к полю напрямую, а не через геттеры и сеттеры.

EDIT:

Как уже упоминалось, использование cascade merge() будет сохранять новые объекты, а также объединять измененные объекты и повторно присоединять отдельные элементы, которые имеют отношение к каскадной опции MERGE. Использование опции PERSIST-каскада не приведет к повторному подключению или слиянию чего-либо и предназначено для использования, когда это будет поведение, которое вы хотите.