Использование Transient Entity в Hibernate для обновления/объединения существующего постоянного объекта

Я имею дело с довольно сложным графом объектов в моей базе данных. Я использую XStream для сериализации и десериализации этого графа объектов, который отлично работает. Когда я импортирую объектный граф объекта, который существует в базе данных, он изначально является временным, поскольку идентификаторы отсутствуют, и hibernate ничего о нем не знает. Затем у меня есть бизнес-логика, которая устанавливает идентификаторы для частей моего графа объектов, выясняя, какие объекты в новой переходной импортированной карте объектов соответствуют существующим постоянным объектам. Затем я использую функции merge() и saveOrUpdate() из Hibernate.

Некоторый псевдокод, чтобы дать вам лучшее представление о том, что я делаю:

ComplexObject transObj = xstream.import("object.xml");
ComplexObject persistObj = someService.getObjByName(transObj.getName());
for (OtherObject o : c.getObjects()) {
    if (persistObj.getObjects().contains(o.getName())) {
        o.setId(persistObj.getObjectByName(o.getName()).getId())
    }
    ... set a bunch of other IDs deeper in the object graph ...
}

transObj = session.merge(transObj);
session.saveOrUpdate(transObj);

Теперь это не работает, так как я получаю такие ошибки, как:

   org.springframework.dao.InvalidDataAccessApiUsageException: deleted object would be re-saved by cascade (remove deleted object from associations): [com.......SomeObject#353296]; nested exception is org.hibernate.ObjectDeletedException: deleted object would be re-saved by cascade (remove deleted object from associations): [com.......SomeObject#353296]

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

Есть ли способ добиться того, что я хочу сделать, без необходимости получать постоянный объект в сеансе и изменять его вместо изменения временного объекта и пытаться сохранить его и переопределить существующий постоянный объект?


person eipark    schedule 15.03.2013    source источник
comment
Вот как я бы это решил: stackoverflow.com/questions/4779239/ - по соображениям производительности вы должны либо кэшировать данные отражения/самоанализа, либо создать генератор классов из ваших сущностей.   -  person user1050755    schedule 18.03.2013
comment
Наличие setId всегда плохая идея...?   -  person Rob    schedule 18.03.2013
comment
user1050755, единственное, что касается этого решения, это то, что оно, похоже, не может обрабатывать сложные каскады, «один ко многим» и т. д. Я полагаю, что это будет работать только для структуры типа, отличной от графа, но поправьте меня, если я м неправильно.   -  person eipark    schedule 18.03.2013
comment
Нельзя ли сделать это наоборот? Я имею в виду сначала загрузить объект (ы) из Hibernate, а затем написать собственный маршаллер из XStream?   -  person mindas    schedule 19.03.2013
comment
Это именно то, чего я пытаюсь избежать. Это может быть возможно, но тогда это становится кошмаром обслуживания каждый раз, когда модель данных меняется, и это будет трудно сделать из-за графообразной природы данных.   -  person eipark    schedule 19.03.2013
comment
Если бы один из пунктов был хорош для вас, смогли бы вы его принять? Вопрос еще открыт.   -  person Glen Best    schedule 29.05.2013


Ответы (2)


похоже, слияние в спящем режиме не предназначалось для связывания временных объектов с постоянными

Слияние является стандартом JPA - объединяет «новые и отдельные экземпляры объекта» с «экземплярами управляемого объекта (контекст сохраняемости)». Будет каскадироваться между отношениями FK, помеченными Cascade.MERGE или Cascade.ALL.

С точки зрения JPA — нет, слияние не предназначалось для связывания ваших потоковых временных объектов Xstream с постоянными. JPA предназначен для слияния, чтобы работать с обычным жизненным циклом JPA - создавать новые объекты, сохранять их, получать/находить их, отсоединять их, изменять их (включая добавление новых объектов), объединять их, при необходимости изменять их еще немного, затем сохранять/сохранять их . Это преднамеренный дизайн, так что JPA оптимизирован и эффективен - каждому отдельному объекту, сохраняемому в базе данных, не нужно предшествовать поиск состояния объекта, чтобы определить, нужно ли/что вставлять/обновлять. Состояние объекта контекста персистентности JPA уже содержит достаточно подробностей, чтобы определить это.

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

SaveOrUpdate — это проприетарная операция гибернации: если у объекта есть идентификатор, он обновляется, но если у него нет объекта, он вставляется.

С точки зрения hibernate - да, слияние с последующим saveOrUpdate теоретически может работать для связывания (потоковых Xstream) временных объектов с постоянными, но у него могут быть ограничения при использовании в сочетании с операциями JPA. saveOrUpdate ДЕЙСТВИТЕЛЬНО предшествует каждому объекту, сохраняемому в базе данных, с извлечением, чтобы определить, нужно ли/что вставлять/обновлять - это умно, но это не JPA и не самое производительное. то есть вы должны быть в состоянии заставить это работать с некоторой осторожностью и правильной конфигурацией - и использовать операции гибернации, а не операции JPA, когда возникает конфликт.

Я получаю сообщения об ошибках, например:

org.hibernate.ObjectDeletedException: удаленный объект будет повторно сохранен каскадно (удаление удаленного объекта из ассоциаций): [com.......SomeObject#353296]

Я считаю, что это может быть вызвано двумя факторами:

  • где-то в вашем (созданном Xstream) графе объектов отсутствует экземпляр дочернего объекта, который присутствовал бы, если бы вы выполнили (каскадное) извлечение с использованием JPA: это дает удаление объекта
  • где-то еще в вашем (созданном Xstream) графе объектов присутствует эта же дочерняя сущность: это дает повторно сохраняемый объект

    Как это отладить: (а) создать граф объектов из Xstream и распечатать его ПОЛНОСТЬЮ - каждый объект, каждое поле; (b) загрузите тот же граф объектов через JPA, каскадно извлеките из верхнего объекта и распечатайте его ПОЛНОСТЬЮ - каждый объект, каждое поле (c) сравните два - что-то отсутствует в (a), что присутствует в (b )??

Есть ли способ добиться того, что я хочу сделать, без необходимости получать постоянный объект в сеансе и изменять его вместо изменения временного объекта и пытаться сохранить его и переопределить существующий постоянный объект?

Надеюсь, с помощью только что предложенной отладки/исправления.

ИЛИ мое грубое предложение (глупо) обойти эту ошибку (хотя производительность немного замедляется): после того, как вы заполните идентификаторы в графе объектов, вызовите EntityManager.clear(), ЗАТЕМ продолжите слияние() и saveOrUpdate() . Но UNIT TEST результаты БД - чтобы убедиться, что вы не упустили что-то важное при заполнении вашего графика Xstream.

Для тестирования / в крайнем случае попробуйте saveOrUpdate() без merge(), но тогда вам может потребоваться очистить() менеджер Entity, чтобы избежать исключений Hibernate, таких как NonUniqueObjectException.

Дайте мне знать, если вы узнаете больше / получите больше информации B^)

person Glen Best    schedule 18.03.2013
comment
Я уже пытался сделать clear() и saveOrUpdate() без слияния. Они приводят к другим ошибкам гибернации... Я думаю, например, что копия объекта уже существует и создание временного объекта. Я попробую другие ваши предложения. Спасибо за исчерпывающий ответ. - person eipark; 18.03.2013
comment
Как бы вы напечатали весь объект? По сути, это исходная проблема, над которой я работал, и именно здесь появился XStream. - person eipark; 25.03.2013

    Hibernate merge was not meant for associating transient objects 
to persistent ones.

В идеале слияние() должно работать для вас, потому что вы фактически имеете дело с отдельным объектом. Поскольку вы устанавливаете идентификаторы в «transObj» перед вызовом слияния, спящий режим будет рассматривать их как отсоединенные (не временные).

Я думаю, проблема с вашим кодом в том, что он отключает спящий режим.

В вашем коде вы загружаете «persistObj» из базы данных. Теперь hibernate удерживает этот «persistObj» в сеансе. Затем вы устанавливаете некоторые идентификаторы из «persistObj» в «transObj», а затем вызываете слияние. Некоторые из дочерних объектов в «persistObj» и «transObj» имеют одинаковые идентификаторы, что приводит к путанице в спящем режиме.

ComplexObject transObj = xstream.import("object.xml");
ComplexObject persistObj = someService.getObjByName(transObj.getName());
for (OtherObject o : c.getObjects()) {
    if (persistObj.getObjects().contains(o.getName())) {
        o.setId(persistObj.getObjectByName(o.getName()).getId())
     }
    ... set a bunch of other IDs deeper in the object graph ...
}
transObj = session.merge(transObj);
session.saveOrUpdate(transObj);

Попробуйте вызвать session.clear() после загрузки «persistObj», чтобы спящий режим удалял persistObj и рассматривал только ваш отдельный объект.

ComplexObject transObj = xstream.import("object.xml");
ComplexObject persistObj = someService.getObjByName(transObj.getName());
// Clear the session, so that hibernate removes 'persistObj' from it's cache
session.clear();
for (OtherObject o : c.getObjects()) {
    if (persistObj.getObjects().contains(o.getName())) {
        o.setId(persistObj.getObjectByName(o.getName()).getId())
     }
    ... set a bunch of other IDs deeper in the object graph ...
}
transObj = session.merge(transObj);
session.saveOrUpdate(transObj);
person Sashi    schedule 18.03.2013
comment
Если вы проверите комментарий, который я поставил к ответу Глена, я попытался использовать очистку изначально перед слиянием () и saveOrUpdate (), но это, как правило, вызывало другие проблемы. Я попробую еще раз и придумаю более четкий ответ, почему это не сработало, или, по крайней мере, некоторые сообщения об ошибках. Спасибо. - person eipark; 18.03.2013
comment
Извините, не увидел другого комментария. Да, пожалуйста, опубликуйте ошибки, которые вы видите с помощью clear(). Я определенно не стал бы привязывать «persistObt» и «tranObj» к одному и тому же сеансу, потому что вы пытаетесь представить одну и ту же строку базы данных, используя два разных экземпляра объекта. Спящий режим обычно не позволяет этого. - person Sashi; 18.03.2013
comment
Я только что попробовал это снова. Я запустил clearSession(), merge(), затем saveOrUpdate(). Как и в моем первоначальном вопросе, я получаю сообщение об удаленном объекте, которое будет пересохранено. - person eipark; 19.03.2013
comment
Могу я спросить, где вы разместили «session.clear ()». Это сразу после загрузки «persistObj»? - person Sashi; 19.03.2013
comment
Я вызвал session.clear() непосредственно перед вызовом session.merge(). - person eipark; 19.03.2013
comment
Вызывает ли слияние () исключение или это saveOrUpdate ()? - person Sashi; 19.03.2013
comment
Я думаю о saveOrUpdate(), но я проверю, когда завтра вернусь на работу. Он просто говорит прокси и не дает номер строки, поэтому мне придется отлаживать строку за строкой. Все транзакционно обернуто, поэтому я думаю, что это происходит в конце моего метода. - person eipark; 19.03.2013
comment
При слиянии() я получаю java.lang.IllegalStateException: An entity copy was already assigned to a different entity. И это несмотря на вызов clearSession() прямо перед слиянием. - person eipark; 20.03.2013
comment
Похоже, что hibernate все еще имеет какую-то ссылку на старый объект, несмотря на вызов session.clear(). Я бы посоветовал вам реорганизовать свой код таким образом, чтобы вы загружали «persistObj» в один сеанс гибернации. Используйте 'persistObj' для заполнения ваших идентификаторов за пределами области транзакции. А затем вызовите «saveOrUpdate» (без слияния) в новом сеансе гибернации. - person Sashi; 20.03.2013
comment
Да, похоже, это так. Я попробую. - person eipark; 21.03.2013