Почему открытая сессия 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 для более широкого обсуждения этой темы. Автор перечисляет три важных момента:
- каждая ленивая инициализация даст вам запрос, означающий, что каждой сущности потребуется N + 1 запрос, где N - число ленивых ассоциаций. Если на вашем экране представлены табличные данные, чтение журнала Hibernate является большой подсказкой, которую вы делаете не так, как следует
- это полностью разрушает многоуровневую архитектуру, так как вы забиваете свои ногти с помощью DB на уровне представления. Это концептуальный довод, поэтому я мог бы жить с этим, но есть следствие
- И последнее, но не менее важное: если при извлечении сеанса возникает исключение, оно возникает во время написания страницы: вы не можете представить пользователю чистую страницу с ошибкой, и единственное, что вы можете сделать, это написать сообщение об ошибке в теле.
транзакции могут быть зафиксированы на уровне сервиса - транзакции не связаны с 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 границы транзакций часто теряются, поскольку сеанс остается открытым на протяжении всего цикла запрос-ответ. Это затрудняет эффективное управление транзакциями и может привести к таким проблемам, как незафиксированные или длительные транзакции, что может повлиять на ограничения подключений к источникам данных, и в конечном итоге все транзакции могут находиться в состоянии «бездействия в транзакции » .