Почему Hibernate Open Session in View считается плохой практикой?

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

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

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

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

Ответ 1

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

Понимание:

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

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

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

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


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

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

Ответ 2

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

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

введите описание изображения здесь

  • 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 Загрузка

К сожалению, Открыть сеанс в режиме просмотра по умолчанию включен в Spring Загрузка.

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

spring.jpa.open-in-view=false

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

Ответ 3

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

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

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

Ответ 4

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

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

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

Ответ 5

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

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

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

Ответ 6

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

Ответ 7

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

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

Ответ 8

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

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

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

Ответ 9

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

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