El contexto de esta pregunta está dentro de spring-boot, usando spring-data-jpa e hibernate.
Un colega escribió un @Service y anotó el método de servicio con @Transactional . El método de servicio carga una entidad y, posteriormente, accede a una colección cargada de forma diferida de uno a muchos ( fetch = FetchType.LAZY ). El método de servicio es invocado por algún delegador personalizado, al que volveré. Esto funciona bien cuando se invoca desde un punto final @RestController .
Cuando invoqué el servicio desde una ruta de camello (nuevamente a través del delegador personalizado) vomitó con una excepción de inicialización perezosa.
Al excavar, descubrió que el servicio implementa una interfaz, el delegador personalizado busca el servicio (se inyecta, por lo que tiene el proxy adecuado) y llama a un método en la interfaz que en realidad es un método predeterminado de Java-8. Este método predeterminado luego llama localmente al método @Transactional .
Así que ahí está el problema: esta es una llamada de método LOCAL, por lo que no se realiza el aspecto/proxy de la anotación @Transactional (usamos aspectJAutoProxy), por lo que el método NO se invoca dentro de una transacción, por lo que la carga diferida DEBERÍA fallar. Y para volver a verificar, también lo probé a través de una anotación @Scheduled : mismo comportamiento. Vomita como debería.
Mi pregunta: Entonces, ¿por qué funciona cuando se llama desde @RestController ? ¡Esto me está volviendo loco!
No hay ninguna anotación transaccional en el punto final del controlador de descanso.
Agregué un código de depuración al servicio usando TransactionSynchronizationManager.isActualTransactionActive() y muestra que en ningún caso hay una transacción, incluso cuando se llama a través del punto final del controlador.
Entonces, ¿por qué funciona la carga diferida cuando se llama desde el controlador? Descargué todo el SQL y en ningún momento la colección perezosa ya está cargada, por lo que no están en ningún caché de hibernación.
Recuerdo haber leído una vez que la carga diferida era una pista, no un comando, pero aun así... ¿por qué funciona en ese caso?
después de estar perplejos por esto en muchas ocasiones, me he topado con la respuesta:
sprint-boot está haciendo un administrador de entidad abierta a la vista a nuestras espaldas a través del interceptor OpenEntityManagerInView. No tenía idea de que esto estaba pasando.
Vea esta excelente respuesta de Vlad Mihalcea https://stackoverflow.com/a/48222934/208687
Cuando su método anota en la sesión de hibernación transaccional, se cierra después del método de retorno, si el objeto que regresa del método tiene la propiedad perezosa, la propiedad perezosa no se carga y obtiene una excepción de que la sesión se cierra. Puede usar buscar en la consulta o usar OSIV