JPA-каскад сохраняется и ссылки на отдельные объекты выдают исключение PersistentObjectException.Зачем? - PullRequest
33 голосов
/ 28 ноября 2010

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

@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 управляемый требует кода, подобного этому, перед сохранением:

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 или я что-то упустил?

Ответы [ 2 ]

21 голосов
/ 28 ноября 2010

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

foo = entityManager.merge(foo); 

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

7 голосов
/ 28 ноября 2010

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

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

EDIT:

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...