Рубрики
Без рубрики

Открытая Сессия С Учетом Анти-Паттерна

Автор оригинала: Vlad Mihalcea.

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

Анти-шаблон (или антипаттерн) – это обычная реакция на повторяющуюся проблему, которая обычно неэффективна и может быть крайне контрпродуктивной.

При использовании JPA и гибернации политика выборки может оказать одно из самых больших влияний на производительность приложения, и, как объясняется в моей Высокопроизводительной презентации JDBC , вы всегда должны получать ровно столько данных , сколько вам нужно для выполнения требований данного варианта использования бизнес-логики. Выборка слишком большого количества столбцов, чем необходимо, оказывает влияние, и именно поэтому сущности не являются хорошими кандидатами для представлений только для чтения. В свою очередь, ДЛЯ прогнозов лучше подходят для наборов данных, доступных только для чтения.

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

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

Hibernate поставляется с прокси-серверами, которые позволяют разработчику приложения откладывать извлечение до тех пор, пока не потребуется ассоциация. Это очень полезно, особенно с точки зрения производительности. Самое худшее, что можно сделать, – это использовать НЕТЕРПЕЛИВЫЕ ассоциации , потому что, как только отношение настроено на нетерпеливую выборку, его нельзя изменить на ленивую выборку на основе каждого запроса. По этой причине многие ассоциации настроены с типом FetchType.ЛЕНИВЫЙ атрибут.

Однако для инициализации прокси-сервера ЛЕНИВОЙ ассоциации необходимо открыть Сеанс . Если контекст сохранения закрыт, при попытке доступа к неинициализированной ЛЕНИВОЙ ассоциации возникает печально известное исключение LazyInitializationException .

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

Только бизнес-уровень несет ответственность за получение всех данных, необходимых для конкретного бизнес-варианта использования. Для ассоциаций “многие к одному” и “один к одному”, а также не более чем для одной связи "один ко многим" директива JOIN FETCH является лучшим способом инициализации ассоциаций, которые понадобятся на уровне представления. Для множественных один ко многим ассоциаций, чтобы избежать декартова произведения, необходимо использовать вторичные запросы. Эти вторичные запросы могут быть запущены при первом доступе к ассоциации, что можно сделать с помощью утилиты Hibernate.initialize(прокси) .

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

  • OpenSessionInViewFilter вызывает OpenSession метод базового SessionFactory и получает новый Сеанс .
  • Сеанс привязан к TransactionSynchronizationManager .
  • OpenSessionInViewFilter вызывает doFilter сервлета javax.Цепочка фильтров ссылка на объект и запрос обрабатываются дополнительно
  • Вызывается DispatcherServlet , и он направляет HTTP-запрос на базовый Postscontroller .
  • Почтовый контроллер вызывает Почтовую службу , чтобы получить список Почтовых сущностей.
  • Почтовая служба открывает новую транзакцию, и HibernateTransactionManager повторно использует тот же Сеанс , который был открыт OpenSessionInViewFilter .
  • PostDAO извлекает список Post сущностей без инициализации какой-либо ленивой ассоциации.
  • Почтовая служба фиксирует базовую транзакцию, но Сеанс не закрывается, поскольку он был открыт извне.
  • DispatcherServlet запускает визуализацию пользовательского интерфейса, который, в свою очередь, перемещается по отложенным ассоциациям и запускает их инициализацию.
  • Фильтр OpenSessionInViewFilter может закрыть Сеанс , а также освободить базовое соединение с базой данных.

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

Уровень обслуживания открывает и закрывает транзакцию базы данных, но после этого явная транзакция не выполняется. По этой причине каждая дополнительная инструкция, выполняемая на этапе рендеринга пользовательского интерфейса, выполняется в режиме автоматической фиксации. Автоматическая фиксация оказывает давление на сервер базы данных, поскольку каждая инструкция должна сбрасывать журнал транзакций на диск, что приводит к большому трафику ввода-вывода на стороне базы данных. Одной из оптимизаций было бы отметить Соединение как доступное только для чтения, что позволило бы серверу базы данных избежать записи в журнал транзакций.

Больше нет разделения проблем, поскольку операторы генерируются как уровнем обслуживания, так и процессом визуализации пользовательского интерфейса. Написание интеграционных тестов, которые утверждают количество генерируемых операторов, требует прохождения всех уровней (веб, служба, DAO) при развертывании приложения в веб-контейнере. Даже при использовании базы данных в памяти (например, HSQLDB) и легкого веб-сервера (например Jetty), эти интеграционные тесты будут выполняться медленнее, чем если бы слои были разделены, а внутренние интеграционные тесты использовали базу данных, в то время как интерфейсные интеграционные тесты вообще издевались над уровнем сервиса.

Уровень пользовательского интерфейса ограничен навигацией по ассоциациям, которые, в свою очередь, могут вызвать N+1 проблем с запросами . Хотя Hibernate предлагает @BatchSize для пакетной выборки ассоциаций и Режим выборки.ВЫБЕРИТЕ чтобы справиться с этим сценарием, аннотации влияют на план выборки по умолчанию, поэтому они применяются к каждому бизнес-варианту использования. По этой причине запрос уровня доступа к данным гораздо более подходит, поскольку он может быть адаптирован к текущим требованиям к извлечению данных в случае использования.

И последнее, но не менее важное: подключение к базе данных сохраняется на протяжении всего этапа визуализации пользовательского интерфейса, что увеличивает время аренды подключения и ограничивает общую пропускную способность транзакций из-за перегрузки пула подключений к базе данных. Чем дольше удерживается соединение, тем больше других одновременных запросов будет ждать получения соединения из пула.

После долгих дебатов хорошо, что Spring Boot выдает предупреждение, что Открытый сеанс В режиме просмотра активен .

OpenSessionInView-это решение проблемы, которая не должна существовать в первую очередь, и наиболее вероятная первопричина заключается исключительно в извлечении сущности. Если на уровне пользовательского интерфейса требуется только представление базовых данных, то уровень доступа к данным будет работать намного лучше с проекцией DTO.

Проекция DTO заставляет разработчика приложения извлекать только необходимый набор данных и не подвержен исключениям(исключениям) LazyInitializationException . Таким образом, разделение проблем больше не ставится под угрозу, и оптимизация производительности может быть применена на уровне уровня доступа к данным, поскольку все операторы ограничены границами выполняемой в данный момент транзакции.