Каков правильный способ повторного прикрепления отдельных объектов в Hibernate?

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

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

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

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

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

Ответ 1

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

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

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

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

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

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

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

Mik

Ответ 2

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

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

Ответ 3

Все эти ответы пропускают важное различие. 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(), а не update(). Если вы не хотите, чтобы все еще не вставленные данные обрабатывались здесь, используйте update() вместо этого...

Ответ 4

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

session.lock(entity, LockMode.NONE);

Он ничего не заблокирует, но получит объект из кеша сеанса или (если его там не найти) прочитает его из базы данных.

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

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

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

Ответ 5

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

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

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

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

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

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

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

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

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

Ответ 6

Я сделал это в С# с 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;
}

Ответ 7

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, которые мне нравятся, различные параметры прикрепления к измененным объектам и их свойствам.

Ответ 8

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

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);
}

Ответ 9

Извините, не могу добавить комментарии (пока?).

Использование Hibernate 3.5.0-Final

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

Итак, мой метод attach немного похож на

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

Первоначальные тесты показывают, что это работает.

Ответ 10

попробуйте getHibernateTemplate(). replicate (entity, ReplicationMode.LATEST_VERSION)

Ответ 11

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

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

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

Ответ 12

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

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

и как необязательный второй шаг (чтобы получить кеши недействительными):

em.refresh(obj)

Ответ 13

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

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

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

Ответ 14

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

Ответ 15

try getHibernateTemplate().saveOrUpdate()