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

108

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

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

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

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

HeDinges
источник
12
Считается ли OSIV плохой практикой? Кем?
Johannes Brodwall
4
И - какие есть хорошие альтернативы?
Дэвид Рабиновиц
7
Этот кусок текста от разработчиков швов: с этой реализацией есть несколько проблем, самая серьезная из которых заключается в том, что мы никогда не можем быть уверены, что транзакция будет успешной, пока мы ее не зафиксируем, но к тому времени, когда транзакция «открытый сеанс в поле зрения» будет зафиксирована, представление полностью отрисовано, и отрисованный ответ, возможно, уже был передан клиенту. Как мы можем уведомить пользователя о том, что его транзакция не удалась?
darpet
2
См. Этот пост в блоге, чтобы узнать о плюсах и минусах, а также о моем собственном опыте - blog.jhades.org/open-session-in-view-pattern-pros-and-cons
Angular University

Ответы:

46

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

Понимание :

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

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

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

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


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

  1. каждая ленивая инициализация даст вам запрос, означающий, что каждой сущности потребуется N + 1 запросов, где N - количество ленивых ассоциаций. Если на вашем экране представлены табличные данные, чтение журнала Hibernate - большой намек на то, что вы делаете не так, как должны.
  2. это полностью разрушает многоуровневую архитектуру, так как вы запачкаете ногти с помощью БД на уровне представления. Это концептуальный обман, так что я мог бы смириться с этим, но есть следствие
  3. И последнее, но не менее важное: если во время выборки сеанса возникает исключение, оно произойдет во время записи страницы: вы не можете представить пользователю чистую страницу с ошибкой, и единственное, что вы можете сделать, это написать сообщение об ошибке в теле
Роберт Мунтяну
источник
13
Хорошо, он «загрязняет» слой представления исключением спящего режима. Но что касается производительности, я думаю, что проблема очень похожа, чем на доступ к уровню обслуживания, который вернет ваш dto. Если вы столкнулись с проблемой производительности, вам следует оптимизировать эту конкретную проблему с помощью более умного запроса или более легкого dto. Если вам нужно разработать слишком много методов обслуживания для обработки возможностей, которые могут вам понадобиться в представлении, вы также «загрязняете» уровень обслуживания. нет?
HeDinges
1
Одно отличие состоит в том, что он задерживает закрытие сеанса Hibernate. Вы будете ждать, пока JSP будет визуализирован / записан / и т. Д., И это позволит сохранить объекты в памяти дольше. Это может быть проблемой, особенно если вам нужно записать данные при фиксации сеанса.
Роберт Мунтяну,
8
Нет смысла говорить, что OSIV вредит производительности. Какие есть альтернативы, кроме использования DTO? В этом случае у вас всегда будет более низкая производительность, потому что данные, используемые любым представлением, должны будут загружаться даже для представлений, которым они не нужны.
Johannes Brodwall
11
Я думаю, что загрязнение работает наоборот. Если мне нужно быстро загрузить данные, уровень логики (или, что еще хуже, уровень доступа к данным) должен знать, каким образом будет отображаться объект. Измените вид, и вы в конечном итоге загрузите то, что вам не нужно, или пропустите нужные вам объекты. Исключение Hibernate - это ошибка, столь же опасная, как и любое другое неожиданное исключение. Но производительность - это проблема. Проблемы с производительностью и масштабируемостью заставят вас больше думать и работать над уровнем доступа к данным и, возможно, вынудят закрыть сеанс раньше,
Йенс Шаудер
1
@JensSchauder «Измените представление, и вы в конечном итоге загрузите ненужные вещи или пропустите нужные вам объекты». Это именно то. Если вы измените представление, гораздо лучше загрузить то, что вам не нужно (так как вы, скорее всего, захотите их получить) или выяснить недостающие объекты, как вы получите исключение ленивой загрузки, чем позволить представлению загружаться это лениво, так как это приведет к проблеме N + 1, и вы даже не узнаете, что это происходит. Так что, IMO, лучше уровень сервиса (и вы) знаете, что он отправляет, чем представление, загружающееся лениво, и вы ничего об этом не знаете.
Jeshurun ​​05
40

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

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 Boot по умолчанию включен Open Session in View .

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

spring.jpa.open-in-view=false

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

Влад Михалча
источник
3
Использование Open Session in View с автоматической фиксацией возможно, но не так, как предполагалось разработчиками Hibernate. Поэтому, хотя у Open Session in View есть свои недостатки, автоматическая фиксация не является одним из них, потому что вы можете просто отключить его и по-прежнему использовать.
stefan.m
Вы говорите о том, что происходит внутри транзакции, и это правда. Но этап рендеринга веб-уровня происходит вне Hibernate, поэтому вы получаете режим автоматической фиксации. Имеет смысл?
Влад Михалча,
Я думаю, что это не самый лучший вариант для Open Session in View. Сеанс и транзакция должны оставаться открытыми до тех пор, пока представление не будет отрисовано, тогда нет необходимости в режиме автоматической фиксации.
stefan.m
2
Сессия остается открытой. Но сделки нет. Распространение транзакции на весь процесс также не является оптимальным, поскольку это увеличивает ее длину, а блокировки удерживаются дольше, чем необходимо. Представьте, что произойдет, если представление выдаст исключение RuntimeException. Будет ли откат транзакции из-за сбоя рендеринга пользовательского интерфейса?
Влад Михалча,
Большое спасибо за очень подробный ответ! Я бы изменил руководство только в конце, поскольку пользователи весенней загрузки, вероятно, не будут использовать jpa таким образом.
Skeeve
24
  • транзакции могут быть зафиксированы на уровне обслуживания - транзакции не связаны с OSIV. Это то, Sessionчто остается открытым, а не выполняющаяся транзакция.

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

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

Божо
источник
2
Что касается первого пункта, это, по крайней мере, не верно для исходного OSIV из вики JBoss, он также обрабатывает разграничение транзакций вокруг запроса.
Паскаль Тивент,
@PascalThivent Что заставило вас так думать?
Sanghyun Lee
13

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

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

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

Джеффри Уайзман
источник
5

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

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

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

0sumgain
источник
1
У вас есть указатель на фактическую реализацию сеансов Hibernate, доступную в представлении через bean-компоненты с ограниченным объемом запроса?
Марво
4

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

Давиде
источник
3

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

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

Крис Аптон
источник
1
Как общее практическое правило SO, если вы даете ответ, лучше делать больше, чем просто размещать ссылки в другом месте. Возможно, дайте одно или два предложения или перечислите пункты, дающие суть. Ссылки - это нормально, но вы хотите добавить немного дополнительной ценности. В противном случае вы можете просто прокомментировать и поместить туда ссылку.
DWright
ссылку в этом ответе стоит прочитать, она дает хорошее руководство о том, когда использовать OSIV, а когда нет
ams
1

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

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

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

rjk2008
источник
1

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

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

Себастьян Лорбер
источник