¿Por qué Hibernate Open Session in View se considera una mala práctica?

108

¿Y qué tipo de estrategias alternativas utiliza para evitar las excepciones LazyLoadExceptions?

Entiendo que la sesión abierta a la vista tiene problemas con:

  • Aplicaciones en capas que se ejecutan en diferentes JVM
  • Las transacciones se confirman solo al final, y lo más probable es que le gusten los resultados antes.

Pero, si sabe que su aplicación se ejecuta en una sola máquina virtual, ¿por qué no aliviar su dolor utilizando una estrategia de sesión abierta en vista?

HeDinges
fuente
12
¿OSIV se considera una mala práctica? ¿Por quién?
Johannes Brodwall
4
Y, ¿cuáles son las buenas alternativas?
David Rabinowitz
7
Esta paz de texto es de los desarrolladores de seam: hay varios problemas con esta implementación, el más serio es que nunca podemos estar seguros de que una transacción es exitosa hasta que la confirmamos, pero para cuando se confirma la transacción de "sesión abierta a la vista", la vista está completamente renderizada y es posible que la respuesta renderizada ya se haya descargado al cliente. ¿Cómo podemos notificar al usuario que su transacción no se realizó correctamente?
darpet
2
Vea esta publicación de blog para conocer los pros y los contras y mi propia experiencia al respecto: blog.jhades.org/open-session-in-view-pattern-pros-and-cons
Angular University

Respuestas:

46

Porque enviar Proxies posiblemente no inicializados, especialmente colecciones, en la capa de vista y activar la carga de hibernación desde allí puede ser problemático tanto desde el punto de vista del rendimiento como de la comprensión.

Comprensión :

El uso de OSIV 'contamina' la capa de visualización con preocupaciones relacionadas con la capa de acceso a datos.

La capa de vista no está preparada para manejar lo HibernateExceptionque puede suceder cuando se realiza una carga diferida, pero presumiblemente la capa de acceso a datos sí lo está.

Rendimiento :

OSIV tiende a tirar de la carga de entidades adecuada debajo de la alfombra: tiende a no notar que sus colecciones o entidades se inicializan de manera perezosa (tal vez N + 1). Más conveniencia, menos control.


Actualización: consulte El antipatrón OpenSessionInView para una discusión más amplia sobre este tema. El autor enumera tres puntos importantes:

  1. cada inicialización diferida le dará una consulta, lo que significa que cada entidad necesitará consultas N + 1, donde N es el número de asociaciones diferidas. Si su pantalla presenta datos tabulares, leer el registro de Hibernate es un gran indicio de que no debe hacer lo que debería
  2. esto derrota por completo la arquitectura en capas, ya que mancillas tus uñas con DB en la capa de presentación. Esta es una estafa conceptual, por lo que podría vivir con ella, pero hay un corolario
  3. Por último, pero no menos importante, si se produce una excepción al recuperar la sesión, ocurrirá durante la escritura de la página: no puede presentar una página de error limpia al usuario y lo único que puede hacer es escribir un mensaje de error en el cuerpo.
Robert Munteanu
fuente
13
Ok, 'contamina' la capa de vista con excepción de hibernación. Pero, con respecto al rendimiento, creo que el problema es bastante similar al de acceder a una capa de servicio que devolverá su dto. Si se enfrenta a un problema de rendimiento, debe optimizar ese problema específico con una consulta más inteligente o un dto más ligero. Si tiene que desarrollar demasiados métodos de servicio para manejar las posibilidades que podría necesitar en la vista, también está 'contaminando' la capa de servicio. ¿No?
HeDinges
1
Una diferencia es que retrasa el cierre de la sesión de Hibernate. Esperará a que el JSP se procese / escriba / etc, y eso mantiene los objetos en la memoria por más tiempo. Eso puede ser un problema, especialmente si necesita escribir datos en la confirmación de sesión.
Robert Munteanu
8
No tiene sentido decir que OSIV daña el rendimiento. ¿Qué alternativas existen, excepto el uso de DTO? En ese caso, siempre tendrá un rendimiento más bajo porque los datos utilizados por cualquier vista deberán cargarse incluso para las vistas que no los necesitan.
Johannes Brodwall
11
Creo que la contaminación funciona al revés. Si necesito cargar los datos con impaciencia, la capa lógica (o peor aún, la capa de acceso a datos) necesita saber de qué manera se mostrará un objeto. Cambia la vista y terminas cargando cosas que no necesitas o faltan objetos que necesitas. Una excepción de hibernación es un error y tan envenenado como cualquier otra excepción inesperada. Pero el rendimiento es un problema. Los problemas de rendimiento y escalabilidad lo obligarán a pensar y trabajar más en su capa de acceso a datos, y posiblemente forzarán a que la sesión se cierre antes
Jens Schauder
1
@JensSchauder "Cambia la vista y terminas cargando cosas que no necesitas o faltan objetos que necesitas". Eso es exactamente. Si cambia la vista, es mucho mejor cargar cosas que no necesita (ya que es más probable que esté ansioso por obtenerlas) o descubrir los objetos que faltan como obtendría la excepción de carga diferida, que dejar que la vista se cargue perezosamente, ya que eso resultará en el problema N + 1, y ni siquiera sabrá que está sucediendo. Entonces, en mi opinión, es mejor que la capa de servicio (y usted) sepa lo que se envía que la vista que se carga perezosamente y usted no sabe nada al respecto.
Jeshurun
40

Para obtener una descripción más detallada, puede leer mi Antipatrón en vista de sesión abierta . De lo contrario, aquí hay un resumen de por qué no debería usar Open Session In View.

Open Session In View adopta un enfoque inadecuado para la obtención de datos. En lugar de dejar que la capa empresarial decida cómo es mejor obtener todas las asociaciones que necesita la capa de vista, obliga al contexto de persistencia a permanecer abierto para que la capa de vista pueda activar la inicialización del proxy.

ingrese la descripción de la imagen aquí

  • El OpenSessionInViewFilterllama al openSessionmétodo del subyacente SessionFactoryy obtiene un nuevoSession .
  • El Sessionestá ligado alTransactionSynchronizationManager .
  • Las OpenSessionInViewFilterllamadas doFilterde lajavax.servlet.FilterChain referencia objeto y la solicitud se procesan más.
  • Se DispatcherServletllama y enruta la solicitud HTTP al subyacentePostController .
  • El PostControllerllama a PostServicepara obtener una lista de Postentidades.
  • La PostServiceabre una nueva transacción, y la HibernateTransactionManagervuelve a utilizar el mismo Sessionque fue inaugurado por el OpenSessionInViewFilter.
  • El PostDAOObtiene la lista de Postentidades sin inicializar cualquier forma de asociación perezoso.
  • El PostServiceconfirma la transacción subyacente, pero elSession no está cerrada, ya que se abrió el exterior.
  • los DispatcherServlet aperturas de representación de la interfaz de usuario, lo que, a su vez, navega por las asociaciones perezosos y desencadena su inicialización.
  • El OpenSessionInViewFilterpuede cerrar el Session, y la conexión de base de datos subyacente se libera también.

A primera vista, esto puede no parecer algo terrible, pero, una vez que lo ve desde la perspectiva de una base de datos, una serie de fallas comienzan a volverse más obvias.

La capa de servicio abre y cierra una transacción de base de datos, pero luego, no hay ninguna transacción explícita. Por esta razón, cada declaración adicional emitida desde la fase de representación de la interfaz de usuario se ejecuta en modo de confirmación automática. La confirmación automática ejerce presión sobre el servidor de la base de datos porque cada declaración debe vaciar el registro de transacciones en el disco, lo que provoca una gran cantidad de tráfico de E / S en el lado de la base de datos. Una optimización sería marcar Connectioncomo de solo lectura, lo que permitiría al servidor de la base de datos evitar escribir en el registro de transacciones.

Ya no hay separación de preocupaciones porque las declaraciones son generadas tanto por la capa de servicio como por el proceso de representación de la interfaz de usuario. Escribir pruebas de integración que afirmen la cantidad de declaraciones que se generan requiere pasar por todas las capas (web, servicio, DAO), mientras se implementa la aplicación en un contenedor web. Incluso cuando se utiliza una base de datos en memoria (por ejemplo, HSQLDB) y un servidor web ligero (por ejemplo, Jetty), estas pruebas de integración serán más lentas de ejecutar que si las capas estuvieran separadas y las pruebas de integración de back-end utilizaran la base de datos, mientras que Las pruebas de integración de front-end se burlaban de la capa de servicio por completo.

La capa de interfaz de usuario se limita a las asociaciones de navegación que, a su vez, pueden desencadenar problemas de consulta N + 1. Aunque Hibernate ofrece la @BatchSizeposibilidad de obtener asociaciones en lotes y FetchMode.SUBSELECTpara hacer frente a este escenario, las anotaciones están afectando el plan de recuperación predeterminado, por lo que se aplican a cada caso de uso empresarial. Por esta razón, una consulta de la capa de acceso a datos es mucho más adecuada porque se puede adaptar a los requisitos de obtención de datos del caso de uso actual.

Por último, pero no menos importante, la conexión de la base de datos podría mantenerse durante la fase de representación de la interfaz de usuario (según el modo de liberación de la conexión), lo que aumenta el tiempo de concesión de la conexión y limita el rendimiento general de las transacciones debido a la congestión en el grupo de conexiones de la base de datos. Cuanto más se mantenga la conexión, más solicitudes simultáneas esperarán para obtener una conexión del grupo.

Entonces, o mantiene la conexión durante demasiado tiempo, o adquiere / libera múltiples conexiones para una sola solicitud HTTP, lo que ejerce presión sobre el grupo de conexiones subyacentes y limita la escalabilidad.

Bota de primavera

Desafortunadamente, Open Session in View está habilitado de forma predeterminada en Spring Boot .

Por lo tanto, asegúrese de que en el application.propertiesarchivo de configuración tenga la siguiente entrada:

spring.jpa.open-in-view=false

Esto desactivará OSIV, para que pueda manejar de la LazyInitializationExceptionmanera correcta .

Vlad Mihalcea
fuente
3
Es posible usar Open Session in View con confirmación automática, pero no de la forma en que lo pretendían los desarrolladores de Hibernate. Entonces, aunque Open Session in View tiene sus inconvenientes, el compromiso automático no lo es porque simplemente puede apagarlo y seguir usándolo.
stefan.m
Estás hablando de lo que sucede dentro de una transacción, y eso es cierto. Pero la fase de renderizado de la capa web ocurre fuera de Hibernate, por lo tanto, obtiene el modo de confirmación automática. ¿Tiene sentido?
Vlad Mihalcea
Creo que es una variante que no es la óptima para Open Session in View. La sesión y la transacción deben permanecer abiertas hasta que se haya renderizado la vista, entonces no es necesario el modo de confirmación automática.
stefan.m
2
La sesión permanece abierta. Pero la transacción no lo hace. Abarcar la transacción en todo el proceso tampoco es óptimo, ya que aumenta su duración y los bloqueos se mantienen durante más tiempo del necesario. Imagínese lo que sucede si la vista arroja una RuntimeException. ¿Se revertirá la transacción porque falló la representación de la IU?
Vlad Mihalcea
¡Muchas gracias por una respuesta tan detallada! Solo cambiaría la guía al final, ya que los usuarios de Spring Boot probablemente no usarán jpa de esa manera.
Skeeve
24
  • las transacciones se pueden realizar en la capa de servicio; las transacciones no están relacionadas con OSIV. Es el Sessionque permanece abierto, no una transacción, en ejecución.

  • si las capas de su aplicación están distribuidas en varias máquinas, entonces prácticamente no puede usar OSIV; debe inicializar todo lo que necesita antes de enviar el objeto por el cable.

  • OSIV es una forma agradable y transparente (es decir, ninguno de su código es consciente de que sucede) de hacer uso de los beneficios de rendimiento de la carga diferida

Bozho
fuente
2
Con respecto al primer punto, esto al menos no es cierto para el OSIV original de la wiki de JBoss, también maneja la demarcación de transacciones alrededor de la solicitud.
Pascal Thivent
@PascalThivent ¿Qué parte te hizo pensar eso?
Sanghyun Lee
13

No diría que Open Session In View se considera una mala práctica; que te da esa impresion?

Open-Session-In-View es un enfoque simple para manejar sesiones con Hibernate. Porque es simple, a veces es simplista. Si necesita un control detallado sobre sus transacciones, como tener múltiples transacciones en una solicitud, Open-Session-In-View no siempre es un buen enfoque.

Como han señalado otros, OSIV tiene algunas compensaciones: es mucho más propenso al problema N + 1 porque es menos probable que se dé cuenta de las transacciones que está iniciando. Al mismo tiempo, significa que no necesita cambiar su capa de servicio para adaptarse a cambios menores en su vista.

Geoffrey Wiseman
fuente
5

Si está utilizando un contenedor de Inversión de control (IoC) como Spring, es posible que desee leer sobre el alcance de los beans . Esencialmente, le estoy diciendo a Spring que me dé un Sessionobjeto Hibernate cuyo ciclo de vida abarca toda la solicitud (es decir, se crea y se destruye al principio y al final de la solicitud HTTP). No tengo que preocuparme por LazyLoadExceptioncerrar la sesión, ya que el contenedor IoC lo gestiona por mí.

Como se mencionó, tendrá que pensar en los problemas de rendimiento de N + 1 SELECT. Siempre puede configurar su entidad Hibernate después para realizar una carga de unión ansiosa en lugares donde el rendimiento es un problema.

La solución de alcance de beans no es específica de Spring. Sé que PicoContainer ofrece la misma capacidad y estoy seguro de que otros contenedores de IoC maduros ofrecen algo similar.

0sumgain
fuente
1
¿Tiene un puntero a una implementación real de las sesiones de Hibernate que están disponibles en la vista a través de beans con alcance de solicitud?
Marvo
4

En mi propia experiencia, OSIV no es tan malo. El único arreglo que hice fue usar dos transacciones diferentes: - la primera, abierta en "capa de servicio", donde tengo la "lógica empresarial" - la segunda abierta justo antes de la representación de la vista

Davide
fuente
3

Acabo de publicar una publicación sobre algunas pautas sobre cuándo usar la sesión abierta a la vista en mi blog. Compruébalo si estás interesado.

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

Chris Upton
fuente
1
Como regla general de SO, si está proporcionando una respuesta, es mejor hacer algo más que vincular a otro lugar. Quizás proporcione una o dos oraciones o elementos enumerados dando la esencia. Está bien vincular, pero desea proporcionar un valor extra. De lo contrario, es posible que desee simplemente comentar y colocar el enlace allí.
DWright
Vale la pena leer el enlace en esta respuesta, proporciona una buena orientación sobre cuándo usar OSIV y no
ams
1

Estoy muy oxidado con Hibernate ... pero creo que es posible tener múltiples transacciones en una sesión de Hibernate. Por lo tanto, los límites de su transacción no tienen que ser los mismos que los eventos de inicio / detención de sesión.

OSIV, en mi opinión, es principalmente útil porque podemos evitar escribir código para iniciar un 'contexto de persistencia' (también conocido como sesión) cada vez que la solicitud necesita hacer un acceso a la base de datos.

En su capa de servicio, probablemente necesitará realizar llamadas a métodos que tienen diferentes necesidades de transacción, como 'Requerido, Nuevo Requerido, etc.' Lo único que necesitan estos métodos es que alguien (es decir, el filtro OSIV) haya iniciado el contexto de persistencia, por lo que lo único que tienen que preocuparse es - "Oye, dame la sesión de hibernación para este hilo ... Necesito hacer algo Cosas de DB ".

rjk2008
fuente
1

Esto no ayudará demasiado, pero puede consultar mi tema aquí: * Hibernate Cache1 OutOfMemory con OpenSessionInView

Tengo algunos problemas de OutOfMemory debido a OpenSessionInView y muchas entidades cargadas, porque permanecen en el nivel de caché de Hibernate 1 y no se recolectan basura (cargo muchas entidades con 500 elementos por página, pero todas las entidades permanecen en la caché)

Sebastien Lorber
fuente