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

Безопасная очистка сеанса Hibernate в середине крупной транзакции

Я использую Spring + Hibernate для операции, которая требует создания и обновления буквально сотен тысяч предметов. Что-то вроде этого:

{
   ...
   Foo foo = fooDAO.get(...);
   for (int i=0; i<500000; i++) {
      Bar bar = barDAO.load(i);
      if (bar.needsModification() && foo.foo()) {
         bar.setWhatever("new whatever");
         barDAO.update(bar);
         // commit here
         Baz baz = new Baz();
         bazDAO.create(baz);
         // if (i % 100 == 0), clear
      }
   }
}

Чтобы защитить себя от потери изменений в середине, я фиксирую изменения сразу после barDAO.update(bar):

HibernateTransactionManager transactionManager = ...; // injected by Spring
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus transactionStatus = transactionManager.getTransaction(def);
transactionManager.commit(transactionStatus);

В этот момент я должен сказать, что весь процесс выполняется в транзакции, заключенной в org.springframework.orm.hibernate3.support.ExtendedOpenSessionInViewFilter (да, это веб-приложение).

Все это прекрасно работает с одним исключением: после нескольких тысяч обновлений/коммитов весь процесс становится очень медленным, скорее всего из-за того, что память раздувается все увеличивающимся количеством объектов, хранящихся в Spring/Hibernate.

В среде только для спящего режима это можно легко разрешить, вызвав org.hibernate.Session#clear().

Теперь вопросы:

  • Когда это хорошее время для clear()? Имеет ли он большие эксплуатационные расходы?
  • Почему не выделяются объекты типа bar или baz/GCd? Какой смысл держать их в сеансе после фиксации (в следующем цикле итерации они еще не достижимы)? Я не сделал дампа памяти, чтобы доказать это, но мое хорошее чувство заключается в том, что они все еще там, пока они не вышли полностью. Если ответ на этот вопрос - "Hibernate cache", то почему кеш не загорелся, когда доступная память опустилась?
  • безопасно/рекомендуется напрямую звонить org.hibernate.Session#clear() (имея в виду весь контекст Spring, такие как ленивая загрузка и т.д.)? Существуют ли какие-либо используемые Spring обертки/копии для достижения того же?
  • Если ответ на указанный выше вопрос верен, что произойдет с объектом foo, предполагая, что clear() вызывается внутри цикла? Что делать, если foo.foo() является методом ленивой нагрузки?

Спасибо за ответы.

4b9b3361

Ответ 1

Когда это хорошее время для очистки()? Имеет ли он высокую производительность?

С регулярными интервалами, в идеале, такими же, как размер партии JDBC, после сброса изменений. Документация описывает общие идиомы в главе о Пакетная обработка:

13.1. Пакетные вставки

При создании новых объектов flush(), а затем очистить() сеанс регулярно, чтобы контролировать размер кеша первого уровня.

Session session = sessionFactory.openSession();
Transaction tx = session.beginTransaction();

for ( int i=0; i<100000; i++ ) {
    Customer customer = new Customer(.....);
    session.save(customer);
    if ( i % 20 == 0 ) { //20, same as the JDBC batch size
        //flush a batch of inserts and release memory:
        session.flush();
        session.clear();
    }
}

tx.commit();
session.close();

И это не должно стоить производительности, au contraire:

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

Почему не удаляются объекты, такие как bar или baz/GCd? Какой смысл держать их в сеансе после фиксации (в следующем цикле итерации они все равно недоступны)?

Вам нужно clear() сеанс явно, если вы не хотите отслеживать отслеживаемые объекты, что все, что он работает (можно было бы совершить транзакцию без "потери" сущностей).

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

безопасно/рекомендуется вызывать org.hibernate.Session # clear() напрямую

Пока вы flush() ожидающие изменения не потеряют их (если это не то, что вы хотите), я не вижу никаких проблем с этим (ваш текущий код потеряет создание каждые 100 циклов, но, возможно, это просто некоторый псевдокод).

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

Вызов clear() выдает все загруженные экземпляры из Session, делая их отдельными объектами. Если для последующего вызова требуется, чтобы объект был "прикреплен", он потерпит неудачу.

Ответ 2

Я просто хотел указать, что после очистки сеанса, если вы хотите продолжать использовать некоторые объекты, которые были в сеансе, вам нужно будет Session.refresh(obj) их, чтобы продолжить.

В противном случае вы получите следующую ошибку:

org.hibernate.NonUniqueObjectException