Почему открытая сессия Hibernate считается плохой практикой? - PullRequest
100 голосов
/ 09 июля 2009

А какие альтернативные стратегии вы используете, чтобы избежать LazyLoadExceptions?

Я понимаю, что у открытого сеанса есть проблемы с:

  • Многоуровневые приложения, работающие на разных jvm
  • Транзакции совершаются только в конце, и, скорее всего, вы хотели бы получить результаты раньше.

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

Ответы [ 9 ]

44 голосов
/ 09 июля 2009

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

Понимание

Использование OSIV «загрязняет» слой представления проблемами, связанными со слоем доступа к данным.

Уровень представления не готов к обработке HibernateException, который может произойти при отложенной загрузке, но, предположительно, уровень доступа к данным равен.

Производительность

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


Обновление: см. Антипаттерн OpenSessionInView для более широкого обсуждения этой темы. Автор перечисляет три важных момента:

  1. каждая ленивая инициализация даст вам запрос, означающий, что каждой сущности потребуется N + 1 запрос, где N - количество ленивых ассоциаций. Если на вашем экране представлены табличные данные, то чтение журнала Hibernate является большой подсказкой, которую вы делаете не так, как должны
  2. это полностью побеждает многоуровневую архитектуру, так как вы забиваете свои ногти с помощью DB на уровне представления. Это концептуальный довод, поэтому я мог бы жить с этим, но есть следствие
  3. И последнее, но не менее важное: если при извлечении сеанса возникает исключение, оно возникает во время записи страницы: вы не можете представить пользователю чистую страницу ошибки, и единственное, что вы можете сделать, это написать сообщение об ошибке в тело
36 голосов
/ 30 мая 2016

Более подробное описание вы можете прочитать в моей статье Open Session In View Anti-Pattern . В противном случае, вот краткое изложение того, почему вы не должны использовать Open Session In View.

Open Session In View использует плохой подход к извлечению данных. Вместо того, чтобы позволить бизнес-уровню решать, как лучше выбрать все ассоциации, необходимые для слоя View, он заставляет контекст постоянства оставаться открытым, чтобы уровень View мог инициировать инициализацию Proxy.

enter image description here

  • OpenSessionInViewFilter вызывает метод openSession базового SessionFactory и получает новый Session.
  • Session привязан к TransactionSynchronizationManager.
  • OpenSessionInViewFilter вызывает doFilter ссылки на объект javax.servlet.FilterChain, и запрос дополнительно обрабатывается
  • Вызывается DispatcherServlet, и он направляет HTTP-запрос к базовому PostController.
  • PostController вызывает PostService, чтобы получить список Post сущностей.
  • PostService открывает новую транзакцию, а HibernateTransactionManager повторно использует тот же Session, который был открыт OpenSessionInViewFilter.
  • PostDAO извлекает список Post сущностей без инициализации ленивых ассоциаций.
  • PostService фиксирует основную транзакцию, но Session не закрывается, поскольку он был открыт извне.
  • DispatcherServlet начинает рендеринг пользовательского интерфейса, который, в свою очередь, перемещается по ленивым ассоциациям и запускает их инициализацию.
  • OpenSessionInViewFilter может закрыть Session, и базовое соединение с базой данных также освобождается.

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

Сервисный уровень открывает и закрывает транзакцию базы данных, но после этого не происходит явной транзакции. По этой причине каждый дополнительный оператор, выдаваемый на этапе визуализации пользовательского интерфейса, выполняется в режиме автоматической фиксации. Автоматическая фиксация оказывает давление на сервер базы данных, поскольку каждый оператор должен сбрасывать журнал транзакций на диск, что вызывает большой объем трафика ввода-вывода на стороне базы данных. Одной из оптимизаций было бы пометить Connection только для чтения, что позволило бы серверу базы данных избежать записи в журнал транзакций.

Больше нет разделения на проблемы, потому что операторы генерируются как сервисным уровнем, так и процессом рендеринга пользовательского интерфейса. Для написания интеграционных тестов, которые утверждают количество генерируемых операторов , необходимо пройти через все уровни (веб, сервис, DAO), пока приложение развернуто в веб-контейнере. Даже при использовании базы данных в памяти (например, HSQLDB) и облегченного веб-сервера (например, Jetty) эти интеграционные тесты будут выполняться медленнее, чем если бы слои были разделены, а внутренние интеграционные тесты использовали базу данных, в то время как Фронтальные интеграционные тесты полностью высмеивали сервисный уровень.

Уровень пользовательского интерфейса ограничен навигационными ассоциациями, которые, в свою очередь, могут вызвать проблемы с N + 1 запросами. Хотя Hibernate предлагает @BatchSize для извлечения ассоциаций в пакетах и ​​FetchMode.SUBSELECT, чтобы справиться с этим сценарием, аннотации влияют на план выборки по умолчанию, поэтому они применяются к каждому бизнесу вариант использования. По этой причине запрос уровня доступа к данным является гораздо более подходящим, поскольку он может быть адаптирован к текущим требованиям выборки данных варианта использования.

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

Таким образом, либо вы удерживаете соединение слишком долго, либо вы получаете / освобождаете несколько соединений для одного HTTP-запроса, что оказывает давление на базовый пул соединений и ограничивает масштабируемость.

Spring Boot

К сожалению, Open Session in View включен по умолчанию в Spring Boot .

Итак, убедитесь, что в файле конфигурации application.properties есть следующая запись:

spring.jpa.open-in-view=false

Это отключит OSIV, так что вы можете правильно обработать LazyInitializationException .

24 голосов
/ 21 сентября 2010
  • транзакции могут быть зафиксированы на уровне обслуживания - транзакции не связаны с OSIV. Это Session, которая остается открытой, а не транзакция - выполняется.

  • если уровни ваших приложений распределены по нескольким машинам, то вы почти не можете использовать OSIV - вам нужно инициализировать все, что вам нужно, перед отправкой объекта по проводам.

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

13 голосов
/ 21 июля 2009

Я бы не сказал, что Open Session In View считается плохой практикой; что создает у вас такое впечатление?

Open-Session-In-View - это простой подход к обработке сессий в Hibernate. Потому что это просто, иногда просто. Если вам нужен детальный контроль над транзакциями, например, наличие нескольких транзакций в запросе, Open-Session-In-View - не всегда хороший подход.

Как уже отмечали другие, у OSIV есть некоторые компромиссы - вы гораздо более склонны к проблеме N + 1, потому что у вас меньше шансов понять, какие транзакции вы запускаете. В то же время это означает, что вам не нужно менять уровень обслуживания для адаптации к незначительным изменениям в вашем представлении.

5 голосов
/ 20 июля 2009

Если вы используете контейнер Inover of Control (IoC), такой как Spring, вы можете прочитать о bean scoping . По сути, я говорю Spring предоставить мне объект Hibernate Session, жизненный цикл которого охватывает весь запрос (то есть он создается и уничтожается в начале и в конце HTTP-запроса). Мне не нужно беспокоиться ни о LazyLoadException s, ни о закрытии сеанса, поскольку контейнер IoC управляет этим для меня.

Как уже упоминалось, вам придется подумать о проблемах с производительностью N + 1 SELECT. Впоследствии вы всегда можете настроить свою сущность Hibernate для выполнения активной загрузки соединений в тех местах, где возникает проблема с производительностью.

Решение для определения объема бобов не зависит от Spring. Я знаю, что PicoContainer предлагает такую ​​же возможность, и я уверен, что другие зрелые контейнеры IoC предлагают нечто подобное.

4 голосов
/ 13 октября 2010

По моему опыту, OSIV не так уж и плох. Единственное соглашение, которое я сделал, - это использование двух разных транзакций: - первый, открытый в «сервисном слое», где у меня есть «бизнес-логика» - второе открылось незадолго до рендеринга вида

3 голосов
/ 04 апреля 2010

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

http://heapdump.wordpress.com/2010/04/04/should-i-use-open-session-in-view/

1 голос
/ 21 января 2011

Это не сильно поможет, но вы можете проверить мою тему здесь: * Hibernate Cache1 OutOfMemory с OpenSessionInView

У меня есть некоторые проблемы OutOfMemory из-за OpenSessionInView и большого количества загруженных сущностей, потому что они остаются в кэше Hibernate level1 и не являются сборщиком мусора (я загружаю много сущностей по 500 элементов на страницу, но все сущности остаются в кэше)

1 голос
/ 02 ноября 2009

Я против ржавчины в Hibernate ... но я думаю, что возможно иметь несколько транзакций в одном сеансе Hibernate. Таким образом, границы вашей транзакции не должны совпадать с событиями запуска / остановки сеанса.

OSIV, imo, в первую очередь полезен, потому что мы можем избежать написания кода для запуска «персистентного контекста» (a.k.a. сеанса) каждый раз, когда запрос должен сделать доступ к БД.

На вашем сервисном уровне вам, вероятно, потребуется совершать вызовы методов, которые имеют различные потребности транзакций, такие как «Обязательный, Новый Обязательный и т. Д.» Единственное, что нужно этим методам, это то, что кто-то (то есть фильтр OSIV) запустил контекст персистентности, так что единственное, о чем он должен беспокоиться, это: «эй, дайте мне сеанс гибернации для этого потока ... Мне нужно сделать несколько БД вещи ".

...