¿Por qué se considera una mala práctica Hibernate Open Session in View?
¿Y qué tipo de estrategias alternativas utiliza para evitar 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 sólo 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 única máquina virtual, ¿por qué no aliviar su dolor utilizando una estrategia de sesión abierta a la vista?
Open Session In View adopta un mal enfoque para recuperar datos. En lugar de permitir que la capa empresarial decida cuál es la mejor manera de obtener todas las asociaciones que necesita la capa Vista, obliga al Contexto de persistencia a permanecer abierto para que la capa Vista pueda activar la inicialización del Proxy.
- Llama
OpenSessionInViewFilter
alopenSession
método del subyacenteSessionFactory
y obtiene un nuevoSession
. - El
Session
está vinculado alTransactionSynchronizationManager
. - Las
OpenSessionInViewFilter
llamadas adoFilter
lajavax.servlet.FilterChain
referencia del objeto y la solicitud se procesan aún más. - Se
DispatcherServlet
llama y enruta la solicitud HTTP al archivoPostController
. PostController
Llama al paraPostService
obtener una lista dePost
entidades.- Abre
PostService
una nueva transacción y reutilizaHibernateTransactionManager
la mismaSession
que abrió elOpenSessionInViewFilter
. - Obtiene
PostDAO
la lista dePost
entidades sin inicializar ninguna asociación diferida. - Confirma
PostService
la transacción subyacente, peroSession
no se cierra porque se abrió externamente. - Comienza
DispatcherServlet
a renderizar la interfaz de usuario, que, a su vez, navega por las asociaciones diferidas y activa su inicialización. - Puede
OpenSessionInViewFilter
cerrar el archivoSession
y la conexión de la base de datos subyacente también se libera.
A primera vista, esto puede no parecer algo terrible, pero, una vez que lo miras 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 se realiza ninguna transacción explícita. Por este motivo, 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 el archivo Connection
como 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 las genera tanto la capa de servicio como 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 liviano (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 usaran la base de datos, mientras que las Las pruebas de integración front-end se burlaban por completo de la capa de servicio.
La capa de interfaz de usuario se limita a navegar por asociaciones que, a su vez, pueden desencadenar problemas de consulta N+1. Aunque Hibernate ofrece @BatchSize
la posibilidad de recuperar asociaciones en lotes y FetchMode.SUBSELECT
hacer frente a este escenario, las anotaciones afectan el plan de recuperación predeterminado, por lo que se aplican a todos los casos de uso empresarial. Por este motivo, una consulta de capa de acceso a datos es mucho más adecuada porque puede adaptarse 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 toda la fase de representación de la interfaz de usuario (dependiendo del modo de liberación de su conexión), lo que aumenta el tiempo de concesión de la conexión y limita el rendimiento general de la transacción 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.
Por lo tanto, 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 subyacente y limita la escalabilidad.
Bota de primavera
Desafortunadamente, Abrir sesión en vista está habilitado de forma predeterminada en Spring Boot .
Entonces, asegúrese de que en el application.properties
archivo de configuración tenga la siguiente entrada:
spring.jpa.open-in-view=false
Esto deshabilitará OSIV, para que pueda manejarlo de LazyInitializationException
la manera correcta, obteniendo todas las asociaciones necesarias mientras está EntityManager
abierto.
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 HibernateException
que puede ocurrir durante la carga diferida, pero presumiblemente la capa de acceso a datos sí lo está.
Actuación :
OSIV tiende a arrastrar la carga de entidades adecuada debajo de la alfombra; tiende a no darse cuenta de que sus colecciones o entidades se inicializan de manera perezosa (quizás N+1). Más comodidad, menos control.
Actualización: consulte El antipatrón OpenSessionInView para obtener una discusión más amplia sobre este tema. El autor enumera tres puntos importantes:
- cada inicialización diferida le proporcionará una consulta, lo que significa que cada entidad necesitará N + 1 consultas, 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 está haciendo lo que debería.
- esto anula completamente la arquitectura en capas, ya que te mancha las uñas con DB en la capa de presentación. Esta es una estafa conceptual, así que podría vivir con ella, pero hay un corolario.
- Por último, pero no menos importante, si ocurre 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.
las transacciones se pueden confirmar en la capa de servicio; las transacciones no están relacionadas con OSIV. Es lo
Session
que 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 cable.
OSIV es una forma agradable y transparente (es decir, ninguno de su código se da cuenta de que esto sucede) de aprovechar los beneficios de rendimiento de la carga diferida.
No diría que Open Session In View se considere una mala práctica; ¿Qué te da esa impresión?
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 otros han señalado, OSIV tiene algunas ventajas y desventajas: eres mucho más propenso al problema N+1 porque es menos probable que te des cuenta de qué transacciones estás iniciando. Al mismo tiempo, significa que no necesita cambiar su capa de servicio para adaptarse a cambios menores en su vista.