Почему открытая сессия Hibernate считается плохой практикой?

И какие альтернативные стратегии вы используете для исключения LazyLoadExceptions?

Я понимаю, что открытое заседание имеет проблемы с:

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

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

10 ответов

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

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

  • 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-запроса, что оказывает давление на базовый пул соединений и ограничивает масштабируемость.

Весенний ботинок

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

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

spring.jpa.open-in-view=false

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

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

Понимание:

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

ТЛ; ДР

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

OSIV — это антипаттерн, общие практические причины?

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

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