Почему 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 элементами на страницу, но все сущности остаются в кеше)