Глава 13. Транзакции и параллельное выполнение

Оглавление
  1. 13.1. Сессия и области видимости транзакций
    1. 13.1.1. Единица работы (Unit of work)
    2. 13.1.2. Длинные разговоры (Long conversations)
    3. 13.1.3. Учёт идентичности объекта
    4. 13.1.4. Общие проблемы
  2. 13.2. Разграничение транзакций базы данных
    1. 13.2.1. Неуправляемая среда
    2. 13.2.2. Использование JTA
    3. 13.2.3. Обработка исключений
    4. 13.2.4. Таймаут транзакции
  3. 13.3. Оптимистический контроль параллельного выполнения
    1. 13.3.1. Проверка версии приложения
    2. 13.3.2. Расширенная сессия и автоматическое управление версиями
    3. 13.3.3. Отсоединённые объекты и автоматическое управление версиями
    4. 13.3.4. Настройка автоматического управления версиями
  4. 13.4. Пессимистическая блокировка
  5. 13.5. Способы освобождения соединения

Самым важным моментом в управлении Hibernate и параллельном выполнении является то, что его легко понять. Hibernate напрямую использует JDBC-соединения и ресурсы JTA без добавления каких-либо дополнительных действий по блокировке. Рекомендуется провести некоторое время со спецификациями JDBC, ANSI и изоляции транзакций вашей системы управления базами данных.

Hibernate не блокирует объекты в памяти. Ваше приложение может ожидать поведение, определяемое уровнем изоляции транзакций базы данных. Через Session, который также является кэшем транзакций, Hibernate обеспечивает повторяемые чтения для поиска по идентификатору и запросам сущности и не сообщает запросы, которые возвращают скалярные значения.

В дополнение к управлению версиями для автоматического оптимистического контроля параллельного выполнения, Hibernate также предлагает, используя синтаксис SELECT FOR UPDATE, (младший) API для пессимистической блокировки записей. Оптимистический контроль параллельного выполнения и этот API обсуждаются далее в этой главе.

Обсуждение контроля параллельного выполнения в Hibernate начинается с детального рассмотрения Configuration, SessionFactory и Session, а также транзакций базы данных и длинных разговоров (long conversations).

13.1. Сессия и области видимости транзакций

SessionFactory — это дорогостоящий в плане создания, потокобезопасный объект, предназначенный для совместного использования всеми потоками приложений. Он создается один раз, обычно при запуске приложения, из экземпляра Configuration.

Session — это недорогой в плане создания, потоконебезопасный объект, который следует использовать один раз, а затем отбрасывать для: одного запроса, разговора или отдельной единицы работы. Session не получит JDBC Connection или Datasource, если только это не понадобится. Он не будет потреблять какие-либо ресурсы до тех пор, пока они не будут использованы.

Чтобы уменьшить конфликт блокировок в базе данных, транзакция базы данных должна быть как можно короче. Длинные транзакции базы данных будут препятствовать масштабированию вашего приложения до высокой нагрузки параллельного выполнения. Не рекомендуется открывать транзакцию базы данных во время пока пользователь думает (user think time), пока единица работы не будет завершена.

Какова область видимости единицы работы? Может ли один Hibernate Session проходить несколько транзакций базы данных или это отношение областей видимости «один-к-одному»? Когда вы должны открывать и закрывать Session и как разграничивать транзакции базы данных? Эти вопросы рассматриваются в следующих разделах.

13.1.1. Единица работы (Unit of work)

Сначала давайте определим, что такое «единица работы». Единица работы — это шаблон проектирования, описанный Мартином Фаулером как "[поддерживает] список объектов, присоединённых бизнес-транзакцией, и координирует запись изменений и разрешение проблем параллельного выполнения. «[PoEAA] Другими словами, это серия операций, которые мы хотим отправить в базу данных одним пакетом. В принципе, это одна транзакция, хотя выполнение единицы работы часто будет охватывать несколько транзакций физической базы данных (см. раздел 13.1.2. «Длинные разговоры»). Поэтому мы говорим о более абстрактном понятии транзакции. Термин «бизнес-транзакция» также иногда используется вместо «единица работы».

Не используйте анти-шаблон сессия-на-операцию (session-per-operation): не открывайте и не закрывайте Session для каждого простого вызова базы данных в одном потоке. То же самое верно для транзакций базы данных. Вызов базы данных в приложении производится с использованием запланированной последовательности; они сгруппированы в атомные единицы работы. Это также означает, что автоматическая фиксация (auto-commit) после каждой отдельной инструкции SQL бесполезна в приложении, так как этот режим предназначен для работы с SQL-консолью ad-hoc. Hibernate отключает или ожидает, что сервер приложений отключит, режим автоматической фиксации. Операции с базой данных не являются необязательными. Вся связь с базой данных должна происходить внутри транзакции. Следует избегать поведения в режиме автоматической фиксации данных для чтения, поскольку многие небольшие транзакции вряд ли будут работать лучше, чем одна четко определенная единица работы. Последняя также более удобна и расширяема.

Наиболее распространённым шаблоном в многопользовательском клиент-серверном приложении является сессия-на-запрос (session-per-request) В этой модели запрос от клиента отправляется на сервер, где выполняется постоянный слой (persistence layer) Hibernate. Открывается новый Session Hibernate, и все операции с базой данных выполняются в этой части работы. По завершении работы, и после того, как был подготовлен ответ для клиента, сессия сбрасывается (flush) и закрывается. Используйте единую транзакцию базы данных для обслуживания запроса клиентов, начиная и фиксируя (commit) её при открытии и закрытии Session. Отношения между ними это «один-к-одному», и эта модель идеально подходит для многих приложений.

Проблема лежит в реализации. Hibernate обеспечивает встроенное управление «текущей сессией», чтобы упростить этот шаблон. Начните транзакцию, когда запрос сервера должен быть обработан, и завершите транзакцию до того, как ответ будет отправлен клиенту. Обычными решениями являются ServletFilter, AOP-перехватчик с pointcut в методах обслуживания или контейнер прокси/перехвата. Контейнер EJB является стандартизированным способом реализации сквозных аспектов, таких как разграничение транзакций на сессионных бинах EJB, декларативно с CMT (container-managed transactions (транзакции, управляемые контейнерами)). Если вы используете программное разграничение транзакций, для удобства использования и переносимости кода используйте API транзакций Hibernate, показанный далее в этой главе.

Ваш код приложения может получить доступ к «текущей сессии» для обработки запроса, вызвав sessionFactory.getCurrentSession(). Вы всегда будете получать Session, в области видимости текущей транзакции базы данных. Это должно быть настроено как для локальных ресурсов, так и для среды JTA. См. раздел 2.2 «Контекстные сессии».

Вы можете расширить область видимости Session и транзакции базы данных до тех пор, пока «вид (view) не будет отрисован (rendered)». Это особенно полезно в приложениях сервлетов, которые используют отдельную фазу рендеринга после обработки запроса. Расширение транзакции базы данных до рендеринга вида достигается путем реализации вашего собственного перехватчика. Однако это будет сложно, если вы будете полагаться на EJB с транзакциями, управляемыми контейнерами. Транзакция будет завершена, когда метод EJB вернет управление, прежде чем сможет начаться рендеринг любого вида. См. веб-сайт и форум Hibernate для получения советов и примеров, относящихся к шаблону Open Session in View.

13.1.2. Длинные разговоры (Long conversations)

Шаблон сессия-на-запрос не является единственным способом проектирования единиц работы. Многие бизнес-процессы требуют целого ряда взаимодействий с пользователем, чередующихся с доступом к базе данных. В веб и корпоративных приложениях недопустимо, чтобы транзакция базы данных охватывала взаимодействие с пользователем. Рассмотрим следующий пример:

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

Первая наивная реализация может привести к тому, что Session и транзакция базы данных будут открыты во время пока пользователь думает (user think time), причем блокировки хранятся в базе данных, чтобы предотвратить одновременную модификацию и гарантировать изоляцию и атомарность. Это анти-шаблон, поскольку конфликт блокировок не позволяет масштабировать приложение с увеличением числа одновременных пользователей.

Вам необходимо использовать несколько транзакций базы данных, чтобы реализовать разговор. В этом случае поддержание изоляции бизнес-процессов становится частичной ответственностью слоя (tier) приложения. Один разговор обычно охватывает несколько транзакций базы данных. Он будет атомарным, если только одна из этих транзакций базы данных (последняя) хранит обновлённые данные. Все остальные просто читают данные (например, в диалоге в стиле «Мастера» (wizard-style), охватывающего несколько циклов запрос/ответ). Это проще реализовать, чем может показаться, особенно если вы используете некоторый функционал Hibernate:

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

13.1.3. Учёт идентичности объекта

Приложение может одновременно обращаться к одному и тому же постоянному состоянию в двух разных Session. Однако экземпляр постоянного класса никогда не делится между двумя экземплярами Session. Именно по этой причине существуют два разных понятия идентичности:

Идентичность БД
foo.getId().equals( bar.getId() )
Идентичность JVM
foo==bar

Для объектов, присоединённых к определенному Session (т.е. в области видимости Session), эти два понятия эквивалентны, а идентификатор JVM для идентификации базы данных гарантируется Hibernate. Хотя приложение может одновременно обращаться к «одному и тому же» (постоянная идентичность) бизнес-объекту в двух разных сессиях, два экземпляра будут фактически «разными» (идентичность JVM). Конфликты разрешаются с использованием оптимистического подхода и автоматического управления версиями при времени сброса/фиксации (flush/commit).

Такой подход позволяет Hibernate и базе данных не беспокоиться о параллельном выполнении. Он также обеспечивает наилучшую масштабируемость, поскольку гарантирование идентичности в однопоточных единицах работы означает, что для этого не требуется дорогостоящая блокировка или другие средства синхронизации. Приложению не требуется синхронизировать любой бизнес-объект, если он поддерживает один поток на Session. В рамках Session приложение может безопасно использовать == для сравнения объектов.

Однако приложение, которое использует == за пределами Session, может привести к неожиданным результатам. Это может произойти даже в некоторых неожиданных местах. Например, если вы помещаете два отдельных экземпляра в один и тот же Set, оба могут иметь одинаковый идентификатор базы данных (т.е. они представляют одну запись). Идентификация JVM, однако, по определению не гарантируется для экземпляров в отсоединённом состоянии. Разработчик должен переопределить методы equals() и hashCode() в постоянных классах и реализовать собственное понятие равенства объектов. Существует одна оговорка: никогда не используйте идентификатор базы данных для реализации равенства. Используйте бизнес-ключ, который представляет собой комбинацию уникальных, обычно неизменяемых атрибутов. Идентификатор базы данных будет изменяться, если переходный объект станет постоянным. Если переходный экземпляр (обычно вместе с отсоединёнными экземплярами) удерживается в Set, изменение хэш-кода прерывает контракт Set. Атрибуты для бизнес-ключей не обязательно должны быть такими же стабильными, как первичные ключи базы данных; вам нужно только гарантировать стабильность, пока объекты находятся в одном наборе. См. веб-сайт Hibernate для более подробного обсуждения этой проблемы. Обратите внимание, что это не проблема Hibernate, а просто то, как должны быть реализованы идентификация и равенство Java объектов.

13.1.4. Общие проблемы

Не используйте анти-шаблоны сессия-на-сессию-пользователя (session-per-user-session) или сессия-на-приложение (session-per-application) (однако есть редкие исключения из этого правила). Некоторые из следующих проблем также могут возникать в рекомендуемых шаблонах, поэтому убедитесь, что вы понимаете последствия перед принятием дизайнерского решения:

13.2. Разграничение транзакций базы данных

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

Приложение Hibernate может работать в неуправляемых (то есть автономных, простых приложениях Web или Swing) и управляемых средах J2EE. В неуправляемой среде Hibernate обычно несёт ответственность за свой собственный пул соединений с базой данных. Разработчик приложения должен вручную установить границы транзакций (начать, фиксировать или откатить транзакции базы данных). Управляемая среда обычно предоставляет транзакции, управляемые контейнерами (container-managed transactions (CMT)), причем сборка транзакций определена декларативно (например, в дескрипторах развертывания сессионных бинах EJB). Демонстрация программной транзакции больше не нужна.

Тем не менее, часто желательно поддерживать переносимость уровня персистентности между неуправляемыми локальными средами и системами, которые могут полагаться на JTA, но использовать BMT вместо CMT. В обоих случаях используется программное разграничение транзакций. Hibernate предлагает API-интерфейс оболочки, называемый Transaction, который преобразуется в собственную систему транзакций среды развертывания. Этот API на самом деле является необязательным, но мы настоятельно рекомендуем его использовать, если вы не используете сессионный бин CMT.

Завершение Session обычно включает в себя четыре различные фазы:

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

13.2.1. Неуправляемая среда

Если слой персистентности Hibernate работает в неуправляемой среде, соединения с базой данных обычно обрабатываются простыми (то есть не-DataSource) пулами соединений, из которых Hibernate получает соединения по мере необходимости. Идиома обработки сессии/транзакции выглядит так:

// Идиома неуправляемой среды
Session sess = factory.openSession();
Transaction tx = null;
try {
    tx = sess.beginTransaction();
    // делаем какую-то работу
    ...
    tx.commit();
} catch (RuntimeException e) {
    if (tx != null) tx.rollback();
    throw e; // или пробрасываем исключение
} finally {
    sess.close();
}

Вам не нужно вызывать flush() сессии явно: вызов commit() автоматически запускает синхронизацию в зависимости от раздела «11.10. Сброс (flush) сессии» для сессии. Вызов close() обозначает завершение сессии. Основное значение close() заключается в том, что соединение JDBC будет отстранено сессией. Этот Java-код переносится и работает как в неуправляемых, так и в JTA-средах.

Как было сказано ранее, гораздо более гибкое решение — это встроенное в Hibernate контекстное управление «текущая сессия»:

// Идиома неуправляемой среды с getCurrentSession()
try {
    factory.getCurrentSession().beginTransaction();
    // делаем какую-то работу
    ...
    factory.getCurrentSession().getTransaction().commit();
} catch (RuntimeException e) {
    factory.getCurrentSession().getTransaction().rollback();
    throw e; // или пробрасываем исключение
}

Вы не увидите эти фрагменты кода в обычном приложении; фатальные (системные) исключения должны всегда быть перехвачены на «верху». Другими словами, код, который выполняет вызовы Hibernate в слое персистентности, и код, который обрабатывает RuntimeException (и обычно может только очищать и выходить), находятся в разных слоях. Текущее управление контекстом Hibernate может значительно упростить этот дизайн, обратившись к SessionFactory. Обработка исключений обсуждается далее в этой главе.

Вы должны выбрать org.hibernate.transaction.JDBCTransactionFactory, который по умолчанию, а для второго примера выберите «thread» как ваш hibernate.current_session_context_class.

13.2.2. Использование JTA

Если ваш слой персистентности работает на сервере приложений (например, за сессионными бинах EJB), каждое соединение с источником данных, полученное Hibernate, будет автоматически включено в глобальную транзакцию JTA. Вы также можете установить автономную реализацию JTA и использовать её без EJB. Hibernate предлагает две стратегии интеграции JTA.

Если вы используете транзакции, управляемые бинами (bean-managed transactions (BMT)), Hibernate сообщит серверу приложений для начала и завершения транзакции BMT, если вы используете API транзакций. Код управления транзакцией идентичен неуправляемой среде.

// Идиома BMT
Session sess = factory.openSession();
Transaction tx = null;
try {
    tx = sess.beginTransaction();
    // делаем какую-то работу
    ...
    tx.commit();
} catch (RuntimeException e) {
    if (tx != null) tx.rollback();
    throw e; // или пробрасываем исключение
} finally {
    sess.close();
}

Если вы хотите использовать Session, связанный с транзакциями, то есть метод getCurrentSession() для лёгкого распространения контекста, напрямую используйте API-интерфейс JTA UserTransaction:

// Идиома BMT с getCurrentSession()
try {
    UserTransaction tx = (UserTransaction) new InitialContext()
                            .lookup("java:comp/UserTransaction");
    tx.begin();
    // делаем какую-то работу в Session связанным с транзакцией
    factory.getCurrentSession().load(...);
    factory.getCurrentSession().persist(...);
    tx.commit();
} catch (RuntimeException e) {
    tx.rollback();
    throw e; // или пробрасываем исключение
}

С CMT разграничение транзакций завершается дескрипторами развертывания бина сессии, а не программно. Код сводится к:

// Идиома CMT
 Session sess = factory.getCurrentSession();
 // делаем какую-то работу
 ...

В CMT/EJB даже откат происходит автоматически. Необработанное исключение RuntimeException, выброшенное методом сеансового бина, сообщает контейнеру о необходимости отменить глобальную транзакцию. Вам вообще не нужно использовать API Transaction Hibernate с BMT или CMT, и вы получаете автоматическое всплытие «текущей» Session, связанной с транзакцией.

При настройке фабрики транзакций Hibernate выберите org.hibernate.transaction.JTATransactionFactory, если вы используете JTA напрямую (BMT) и org.hibernate.transaction.CMTTransactionFactory в бине сессии CMT. Не забудьте также установить hibernate.transaction.manager_lookup_class. Убедитесь, что ваш hibernate.current_session_context_class либо не настроен (обратная совместимость), либо установлен на «jta».

Операция getCurrentSession() имеет один недостаток в среде JTA. Существует одно предостережение от использования режима освобождения соединения after_statement, который затем используется по умолчанию. Из-за ограничения спецификации JTA Hibernate не может автоматически очищать любые незакрытые экземпляры ScrollableResults или Iterator, возвращаемые scroll() или iterate(). Вы должны освободить низлежащий курсор базы данных, вызвав ScrollableResults.close() или Hibernate.close(Iterator) явно из блока finally. В большинстве приложений можно легко избежать использования scroll() или iterate() из кода JTA или CMT.

13.2.3. Обработка исключений

Если Session выбрасывает исключение, включая любое SQLException, немедленно откатите транзакции базы данных, вызовите Session.close() и отбросьте экземпляр Session. Некоторые методы Session не оставляют сессию в согласованном состоянии. Никакое исключение, выброшенное Hibernate, не может рассматриваться как подлежащее восстановлению. Убедитесь, что Session будет закрыт вызовом функции close() в блоке finally.

Исключение HibernateException, которое обертывает большую часть ошибок, которые могут возникать на слое персистентности Hibernate, является непроверяемым исключением. Этого не было в более старых версиях Hibernate. По нашему мнению, мы не должны заставлять разработчика приложений перехватывать невосстанавливаемое исключение на низком уровне. В большинстве систем непроверяемые и фатальные исключения обрабатываются в одном из первых кадров стека вызовов метода (то есть в более высоких слоях), и либо пользователю представляется сообщение об ошибке приложения, либо предпринимается какое-либо другое соответствующее действие. Обратите внимание, что Hibernate также может выбрасывать другие непроверяемые исключения, которые не являются исключениями HibernateException. Они не подлежат восстановлению и соответствующие меры должны быть приняты.

Hibernate обертывает SQLExceptions, возникающие при взаимодействии с базой данных в JDBCException. Фактически, Hibernate пытается преобразовать исключение в более значимый подкласс JDBCException. Низлежащее SQLExceptions всегда доступно через JDBCException.getCause(). Hibernate преобразует SQLException в соответствующий подкласс JDBCException, используя SQLExceptionConverter, подключенный к SessionFactory. По умолчанию SQLExceptionConverter определяется настроенным диалектом. Тем не менее, также возможно подключить пользовательскую реализацию. Подробнее см. Javadocs для класса SQLExceptionConverterFactory. Стандартными подтипами JDBCException являются:

13.2.4. Таймаут транзакции

Важной функцией, предоставляемой управляемой средой, такой как EJB, которая никогда не предоставляется для неконтролируемого кода, является таймаут транзакции. Таймауты транзакций гарантируют, что ни одна неверная транзакция не может бесконечно связывать ресурсы, не возвращая пользователю никакого ответа. Вне управляемой (JTA) среды Hibernate не может полностью обеспечить эту функциональность. Однако Hibernate может, по крайней мере, контролировать операции доступа к данным, гарантируя, что взаимные блокировки (deadlocks) и запросы на уровне базы данных с огромными наборами результатов ограничены определенным таймаутом. В управляемой среде Hibernate может делегировать таймаут транзакции JTA. Эта функциональность абстрагируется объектом Transaction Hibernate.

Session sess = factory.openSession();
try {
    // установить таймаут транзакции на 3 секунды
    sess.getTransaction().setTimeout(3);
    sess.getTransaction().begin();
    // делаем какую-то работу
    ...
    sess.getTransaction().commit()
}
catch (RuntimeException e) {
    sess.getTransaction().rollback();
    throw e; // или пробрасываем исключение
}
finally {
    sess.close();
}

setTimeout() не может быть вызван в бине CMT, где таймауты транзакций должны быть определены декларативно.

13.3. Оптимистический контроль параллельного выполнения

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

13.3.1. Проверка версии приложения

В реализации без большой помощи Hibernate каждое взаимодействие с базой данных происходит в новом Session, и разработчик отвечает за перезагрузку всех постоянных экземпляров из базы данных, прежде чем манипулировать ими. Приложение принудительно выполняет собственную проверку версии, чтобы обеспечить изоляцию транзакций в разговорах. Этот подход является наименее эффективным с точки зрения доступа к базе данных. Это подход, наиболее похожий на сущности EJB.

// foo - экземпляр, загруженный предыдущей сессией
session = factory.openSession();
Transaction t = session.beginTransaction();
int oldVersion = foo.getVersion(); session.load( foo, foo.getKey() ); // загрузить текущее состояние if ( oldVersion != foo.getVersion() ) throw new StaleObjectStateException(); foo.setProperty("bar");
t.commit(); session.close();

Свойство version отражается с помощью <version>, и Hibernate автоматически увеличивает его во время сброса (flush), если сущность грязная.

Если вы работаете в среде с низким уровнем параллельных данных и не нуждаетесь в проверке версий, вы можете использовать этот подход и пропустить проверку версии. В этом случае стратегия последняя фиксация выигрывает (last commit wins) являются стратегией по умолчанию для длинных разговоров. Имейте в виду, что это может запутать пользователей приложения, так как они могут потерять обновления без сообщений об ошибках или получат возможность слить (merge) конфликтующие изменения.

Ручная проверка версий возможна только в тривиальных условиях и не применима для большинства приложений. Часто нужно проверять не только отдельные экземпляры, но и полные графы модифицированных объектов. Hibernate предлагает автоматическую проверку версий либо расширенным Session, либо отсоединёнными экземплярами в качестве парадигмы проектирования.

13.3.2. Расширенная сессия и автоматическое управление версиями

Один экземпляр Session и его постоянные экземпляры, которые используются для всего разговора, называются сессия-на-разговор (session-per-conversation). Hibernate проверяет версии экземпляров во время сброса (flush), выбрасывая исключение, если обнаруживается одновременная модификация. Разработчик должен перехватить и обработать это исключение. Общие параметры — это возможность для пользователя слить (merge) изменения или перезапустить бизнес-разговор с актуальными данными.

Session отключается от любого базового JDBC-соединения при ожидании взаимодействия с пользователем. Этот подход является наиболее эффективным с точки зрения доступа к базе данных. Приложение не проверяет версию или не присоединяет отсоединённые экземпляры, а также не перезагружает экземпляры в каждой транзакции базы данных.

// foo — это экземпляр, загруженный ранее старой сессией
Transaction t = session.beginTransaction(); // Получить новое соединение JDBC, начать транзакцию
foo.setProperty("bar");
session.flush(); // Только для последней транзакции в разговоре t.commit(); // Также возвратите соединение JDBC session.close(); // Только для последней транзакции в разговоре

Объект foo знает, на каком Session он был загружен. Начало новой транзакции базы данных на старой сессии получает новое соединение и возобновляет сессию. Фиксация (commit) транзакции базы данных отключает сессию от соединения JDBC и возвращает соединение в пул. После повторного подключения, чтобы принудительно проверить версию данных, которые вы не обновлили, вы можете вызвать Session.lock() с помощью LockMode.READ для любых объектов, которые могли быть обновлены другой транзакцией. Вам не нужно блокировать данные, которые вы обновляете. Обычно вы должны установить FlushMode.MANUAL на расширенном Session, чтобы только последний цикл транзакций базы данных позволял фактически сохранять все изменения, внесенные в этот разговор. Только эта последняя транзакция базы данных будет включать операцию flush(), а затем close() сессию, чтобы завершить разговор.

Этот шаблон является проблемным, если Session слишком велик, чтобы его можно было сохранить во время пока пользователь думает (user think time) (например, HttpSession следует хранить как можно меньше). Поскольку Session также является кэшем первого уровня и содержит все загруженные объекты, мы, вероятно, можем использовать эту стратегию только для нескольких циклов запрос/ответ. Используйте Session только для отдельного разговора, так как он скоро будет иметь устаревшие данные.

Заметка

Более ранние версии Hibernate требовали явного отключения и повторного подключения Session. Эти методы устарели, так как начало и конец транзакции имеют тот же эффект.

Держите отключенный Session близко к слою персистентности. Используйте сессионный бин состояния EJB для хранения Session в трехслойной среде. Не переносите его на веб-слой или даже сериализуйте его на отдельный уровень, чтобы сохранить его в HttpSession.

Расширенный шаблон сессии или сессия-на-разговор (session-per-conversation) сложнее реализовать с автоматическим управлением контекстом текущей сессии. Для этого вам необходимо предоставить собственную реализацию CurrentSessionContext. См. Hibernate Wiki для примеров.

13.3.3. Отсоединённые объекты и автоматическое управление версиями

Каждое взаимодействие с постоянным хранилищем происходит в новом Session. Тем не менее, одни и те же постоянные экземпляры повторно используются для каждого взаимодействия с базой данных. Приложение управляет состоянием отдельных экземпляров, первоначально загруженных в другой Session, а затем повторно присоединяет их с помощью Session.update(), Session.saveOrUpdate() или Session.merge().

// foo - экземпляр, загруженный предыдущей сессией
foo.setProperty("bar");
session = factory.openSession();
Transaction t = session.beginTransaction();
session.saveOrUpdate(foo); // Используйте merge(), если «foo», возможно, уже был загружен
t.commit();
session.close();

Опять же, Hibernate проверит версии экземпляра во время сброса (flush), выбрасит исключение, если встретятся конфликтующие обновления.

13.3.4. Настройка автоматического управления версиями

Вы можете отключить автоматический инкремент версии для определенных свойств и коллекций, установив для атрибута optimistic-lock значение false. Hibernate больше не будет увеличивать версии, если свойство грязное.

Унаследованные схемы баз данных часто статичны и не могут быть изменены. Или другие приложения могут обращаться к одной и той же базе данных и не будут знать, как обращаться с номерами версий или даже с метками времени. В обоих случаях управление версиями не может полагаться на конкретный столбец в таблице. Чтобы принудительно проверить версию с сопоставлением состояния всех полей в строке, но без отображения свойств версии или времени, включите optimistic-lock=«all» в отображении <class>. Это работает только в том случае, если Hibernate может сравнивать старое и новое состояние (то есть, если вы используете один длинный Session, а не сессия-на-запрос-с-отсоединёнными-объектами (session-per-request-with-detached-objects)).

Одновременная модификация может быть разрешена в тех случаях, когда сделанные изменения не перекрываются. Если вы установите optimistic-lock=«dirty» когда отражаете <class>, Hibernate будет сравнивать только грязные поля во время сброса (flush).

В обоих случаях, с выделенными столбцами версии/метками времени или с полным/грязным сопоставлением полей, Hibernate использует единую инструкцию UPDATE с соответствующим предложением WHERE для каждой сущности для выполнения проверки версии и обновления информации. Если вы используете переходное постоянство (transitive persistence) для каскадирования повторной привязки к связанным сущностям, Hibernate может выполнять ненужные обновления. Обычно это не проблема, но триггеры on update в базе данных могут выполняться даже в том случае, если в отсоединённые экземпляры не были внесены изменения. Вы можете настроить это поведение, установив select-before-update=«true» в отображении <class>, заставив Hibernate сделать SELECT для выбора экземпляра, чтобы убедиться, что изменения произошли до обновления записи.

13.4. Пессимистическая блокировка

Не предполагается, что пользователи тратят много времени на заботу о стратегиях блокировки. Обычно достаточно указать уровень изоляции для JDBC-соединений, а затем просто позволить базе данных выполнить всю работу. Однако продвинутые пользователи могут захотеть получить эксклюзивные пессимистические блокировки или повторно получить блокировки в начале новой транзакции.

Hibernate всегда будет использовать механизм блокировки базы данных; он никогда не блокирует объекты в памяти.

Класс LockMode определяет различные уровни блокировки, которые могут быть получены Hibernate. Блокировка обеспечивается следующими механизмами:

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

Если Session.load() вызывается с UPGRADE или UPGRADE_NOWAIT, а запрошенный объект еще не загружен сесиией, объект загружается с помощью SELECT ... FOR UPDATE. Если load() вызывается для объекта, который уже загружен с менее ограничивающей блокировкой, чем запрошенный, Hibernate вызывает lock() для этого объекта.

Session.lock() выполняет проверку номера версии, если указанный режим блокировки READ, UPGRADE или UPGRADE_NOWAIT. В случае UPGRADE или UPGRADE_NOWAIT используется SELECT ... FOR UPDATE.

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

13.5. Способы освобождения соединения

Одним из преимуществ управления соединением JDBC в Hibernate 2.x стало то, что Session будет получать соединение, когда это впервые потребовалось, а затем поддерживать это соединение до закрытия сессии. Hibernate 3.x представил понятие режимов освобождения соединения, которые будут инструктировать сессию как обрабатывать его соединения JDBC. Следующее обсуждение относится только к соединениям, предоставляемым через сконфигурированный ConnectionProvider. Пользовательские подключения находятся за пределами этой дискуссии. Различные режимы освобождения идентифицируются перечисляемыми значениями org.hibernate.ConnectionReleaseMode:

Параметр конфигурации hibernate.connection.release_mode используется для указания режима освобождения. Возможные значения: