2009-05-26 5 views
162

У меня возникла ситуация, когда мне нужно повторно присоединить отдельные объекты к сеансу спящего режима, хотя объект с тем же идентификатором МОЖЕТ уже существовать в сеансе, что вызовет ошибки.Каков правильный способ повторного прикрепления отдельных объектов в Hibernate?

Прямо сейчас, я могу сделать одну из двух вещей.

  1. getHibernateTemplate().update(obj) Это works если и only если an object does не already exist в hibernate session. Исключения выдаются с указанием объекта с данным идентификатором, который уже существует в сеансе, когда он мне понадобится позже.

  2. getHibernateTemplate().merge(obj) Это работает тогда и только тогда, когда объект существует в сеансе спящего режима. Исключения выбрасываются, когда мне нужно, чтобы объект был в сеансе позже, если я его использую.

Учитывая эти два сценария, как я могу в общих чертах присоединять сеансы к объектам? Я не хочу использовать исключения для управления потоком раствора этой проблемы, поскольку должна быть более элегантным решением ...

ответ

-6
try getHibernateTemplate().saveOrUpdate() 
+1

saveOrUpdate выдаст исключение, как и обновление. – Mark

1

попробовать getHibernateTemplate(). Реплицировать (юридическое лицо, ReplicationMode.LATEST_VERSION)

20

Недифференциальный ответ: Возможно, вы ищете расширенный контекст сохранения. Это одна из основных причин, лежащих в основе Seam Framework ... Если вы, в частности, пытаетесь использовать Hibernate весной, проверьте this piece документов Seam.

Дипломатический ответ: Это описано в Hibernate docs. Если вам нужно больше разъяснений, ознакомьтесь с разделом 9.3.2 от Java Persistence with Hibernate, озаглавленным «Работа с отдельными объектами». Я бы сильно рекомендую вам получить эту книгу, если вы делаете что-то большее, чем CRUD с Hibernate.

+0

Ссылка на hibernate docs мертва. – dcp

+0

С http://www.seamframework.org/: «Активная разработка Seam 3 была остановлена ​​Red Hat». Ссылка «этот фрагмент документов Seam» также мертва. – badbishop

11

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

session.lock(entity, LockMode.NONE); 

Он не будет блокировать ничего, но он будет получать объект из кэша сеанса или (если не найден) читать из БД.

Очень полезно предотвращать LazyInitException, когда вы перемещаете отношения из «старого» (например, из объектов HttpSession). Сначала вы «повторно присоединяете» сущность.

Использование get также может работать, за исключением случаев, когда вы получаете сопоставление наследования (которое уже генерирует исключение на getId()).

entity = session.get(entity.getClass(), entity.getId()); 
+2

Я хотел бы повторно связать сущность с сеансом. К сожалению, 'Session.lock (entity, LockMode.NONE)' терпит неудачу, исключая высказывание: не удалось повторно связать неинициализированную временную коллекцию. Как можно преодолеть это? –

+1

На самом деле я был не совсем прав. Использование lock() привязывает вашу сущность, но не другие связанные с ней объекты. Итак, если вы выполняете entity.getOtherEntity(). GetYetAnotherEntity(), у вас может быть исключение LazyInit. Единственный способ, который я знаю, чтобы преодолеть это, - это использовать find. entity = em.find (entity.getClass(), entity.getId(); –

+0

Нет метода 'Session.find()' API. Возможно, вы имеете в виду 'Session.load (Object object, Serializable id)'. –

158

Похоже, что нет возможности повторно привязать устаревшую отдельную сущность в JPA.

merge() будет выталкивать состояние устаревших данных в БД, и перезаписывать любые промежуточные обновления.

refresh() не может быть вызван на отдельный объект.

lock() нельзя назвать на отдельных лица, и даже если бы он мог, и это было прикрепить объект, вызова «замок» с аргументом «LockMode.NONE» это означает, что вы замок, но не замок, - самая контратакая часть дизайна API, которую я когда-либо видел.

Итак, вы застряли. Существует метод detach(), но нет attach() или reattach(). Очевидный шаг в жизненном цикле объекта недоступен для вас.

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

Кажется, единственный способ сделать это - отбросить устаревшую удаленную сущность и выполнить запрос поиска с тем же идентификатором, который попадет в L2 или DB.

Mik

+0

I интересно, есть ли причина, по которой спецификация JPA не разрешает 'refresh()' на отдельных объектах? Просматривая спецификацию 2.0, я не вижу никаких оправданий, просто она не разрешена. – FGreg

+8

Это определенно НЕ верно. JPwH: '* Повторная привязка модифицированного отдельного экземпляра * Отключенный экземпляр может быть подключен к новому сеансу (и управляется этим новым контекстом постоянства), вызвав update() на удаленном объекте. По нашему опыту вам может быть проще чтобы понять следующий код, если вы переименуете метод update() в своем уме, чтобы повторно подключиться(), однако есть веская причина, по которой он называется обновлением. »Более подробно можно найти в разделе 9.3.2 – cwash

+0

Стойкие объекты работают отлично, грязные Флорида ag устанавливается на основе дельта между начальной нагрузкой и значением (значениями) во время промывки(). Отдельные объекты нуждаются и в настоящее время не имеют этой функции. Способ для спящего режима - добавить дополнительный хеш/id для отдельных объектов. И сохраните моментальный снимок последнего состояния удаленного объекта, как и для постоянных объектов. Таким образом, они могут использовать весь существующий код и заставить его работать для отдельных объектов. Таким образом, как отметил @mikhailfranco, мы не будем «подталкивать устаревшее состояние к БД и перезаписывать любые промежуточные обновления». – tom

0

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

1

В исходном посте есть два метода: update(obj) и merge(obj), упомянутые для работы, но в противоположных обстоятельствах. Если это действительно так, то почему бы не попробовать проверить, находится ли объект уже в сеансе, а затем вызвать update(obj), если это так, иначе звоните merge(obj).

Тест на существование в сеансе - session.contains(obj). Поэтому, я думаю, что следующий псевдо-код будет работать:

if (session.contains(obj)) 
{ 
    session.update(obj); 
} 
else 
{ 
    session.merge(obj); 
} 
+2

содержит() проверяет сравнение по ссылке, но функции спящего режима работают по идентификатору базы данных. session.merge никогда не будет вызываться в вашем коде. –

3

Я придумал решение «освежить» объект из постоянного хранилища, который будет приходиться на другие объекты, которые уже могут быть прикреплены к сессии :

public void refreshDetached(T entity, Long id) 
{ 
    // Check for any OTHER instances already attached to the session since 
    // refresh will not work if there are any. 
    T attached = (T) session.load(getPersistentClass(), id); 
    if (attached != entity) 
    { 
     session.evict(attached); 
     session.lock(entity, LockMode.NONE); 
    } 
    session.refresh(entity); 
} 
19

Все эти ответы пропускают важное различие. update() используется для (re) прикрепления графического объекта к сеансу. Объектами, которые вы передаете, являются те, которые сделаны управляемыми.

merge() на самом деле не является API-интерфейсом (re). Уведомление merge() имеет возвращаемое значение? Это потому, что он возвращает вам управляемый граф, который может и не быть графом, который вы его передали. merge() - это JPA API, и его поведение определяется спецификацией JPA. Если объект, который вы передаете для слияния(), уже управляется (уже связанным с сеансом), то это график, с которым работает Hibernate; объект, переданный в тот же объект, возвращаемый из merge(). Если, однако, объект, который вы передаете в merge(), отключен, Hibernate создает новый управляемый граф объектов, который копирует состояние из вашего отдельного графика на новый управляемый граф. Опять же, все это продиктовано и регулируется спецификацией JPA.

С точки зрения общей стратегии «убедитесь, что этот объект управляется или его управляют», это зависит от того, хотите ли вы учитывать еще не вставленные данные.Предполагая, что вы делаете, использовать что-то вроде

if (session.contains(myEntity)) { 
    // nothing to do... myEntity is already associated with the session 
} 
else { 
    session.saveOrUpdate(myEntity); 
} 

Обратите внимание, я использовал saveOrUpdate(), а не обновление(). Если вы не хотите, пока еще не вставленные данные, обрабатываемые здесь, используйте обновление() вместо того, чтобы ...

+2

Это правильный ответ на этот вопрос - дело закрыто! – cwash

+2

'Session.contains (Object)' проверяет по ссылке. Если в сеансе уже есть другой объект, представляющий одну и ту же строку, и вы передаете отдельный экземпляр, вы получите исключение. – djmj

+0

Поскольку 'Session.contains (Object)' проверяет по ссылке, если есть ** другое ** Entity, представляющее одну и ту же строку в сеансе, это wil возвращает false, и оно обновит его. – AxelWass

7

Я сделал это таким образом в C# с NHibernate, но он должен работать так же, как в Java:

public virtual void Attach() 
{ 
    if (!HibernateSessionManager.Instance.GetSession().Contains(this)) 
    { 
     ISession session = HibernateSessionManager.Instance.GetSession(); 
     using (ITransaction t = session.BeginTransaction()) 
     { 
      session.Lock(this, NHibernate.LockMode.None); 
      t.Commit(); 
     } 
    } 
} 

Первый замок был вызван для каждого объекта, потому что Contains всегда был ложным. Проблема в том, что NHibernate сравнивает объекты по идентификатору и типу базы данных. Содержит метод equals, который сравнивается по ссылке, если он не перезаписан. С этой equals метод работает без каких-либо исключений:

public override bool Equals(object obj) 
{ 
    if (this == obj) { 
     return true; 
    } 
    if (GetType() != obj.GetType()) { 
     return false; 
    } 
    if (Id != ((BaseObject)obj).Id) 
    { 
     return false; 
    } 
    return true; 
} 
9

Я вернулся к JavaDoc для org.hibernate.Session и обнаружил следующее:

Переходные случаи могут быть стойкими по телефону save(), persist() или saveOrUpdate() , Стойкие экземпляры могут быть временными, вызывая delete(). Любой экземпляр, возвращаемый методом get() или load(), является постоянным. Отдельные экземпляры могут быть сделаны постоянными, вызывая update(), saveOrUpdate(), lock() или replicate(). Состояние временного или отсоединенного экземпляра также может быть сохранено как новый постоянный экземпляр, вызвав merge().

Таким образом update(), saveOrUpdate(), lock(), replicate() и merge() являются варианты кандидатов.

update(): Будет выдано исключение, если существует постоянный экземпляр с тем же идентификатором.

saveOrUpdate(): Либо сохранить или обновить

lock(): Устаревшие

replicate(): Persist состояние данного отдельных, например, повторное использование текущего значения идентификатора.

merge(): Возвращает постоянный объект с тем же идентификатором. Данный экземпляр не ассоциируется с сеансом.

Следовательно, lock() не следует использовать сразу и исходя из функциональных требований, которые могут быть выбраны одним или несколькими из них.

2

К сожалению, не могут добавлять комментарии (еще?).

Использование Hibernate 3.5.0-Окончательная

В то время как метод Session#lock это осуждается, то Javadoc делает предлагают использовать Session#buildLockRequest(LockOptions)#lock(entity) и если вы убедитесь, что ваши ассоциации имеют cascade=lock, ленивых заряжания не является проблемой либо ,

Итак, мой присоединять метод выглядит как

MyEntity attach(MyEntity entity) { 
    if(getSession().contains(entity)) return entity; 
    getSession().buildLockRequest(LockOptions.NONE).lock(entity); 
    return entity; 

Первоначальные тесты предполагают, что это работает удовольствие.

4

Session.contains(Object obj) проверяет ссылку и не обнаруживает другой экземпляр, который представляет ту же строку и уже прикреплен к нему.

Здесь мое общее решение для объектов с свойством идентификатора.

public static void update(final Session session, final Object entity) 
{ 
    // if the given instance is in session, nothing to do 
    if (session.contains(entity)) 
     return; 

    // check if there is already a different attached instance representing the same row 
    final ClassMetadata classMetadata = session.getSessionFactory().getClassMetadata(entity.getClass()); 
    final Serializable identifier = classMetadata.getIdentifier(entity, (SessionImplementor) session); 

    final Object sessionEntity = session.load(entity.getClass(), identifier); 
    // override changes, last call to update wins 
    if (sessionEntity != null) 
     session.evict(sessionEntity); 
    session.update(entity); 
} 

Это один из немногих аспектов .Net EntityFramework мне нравится, различные присоединять варианты в отношении измененных сущностей и их свойств.

1

Возможно, на Eclipselink он немного отличается от Eclipselink. Чтобы установить обособленные объекты без получения устаревших данных, я обычно делаю:

Object obj = em.find(obj.getClass(), id); 

и в качестве опции второй этап (чтобы получить кэши аннулированы):

em.refresh(obj) 
1

прикрепить этот объект, вы должны используйте merge();

Этот метод принимает в параметре выделение объекта и возвращает объект, который будет прикреплен и перезагружен из базы данных.

Example : 
    Lot objAttach = em.merge(oldObjDetached); 
    objAttach.setEtat(...); 
    em.persist(objAttach);