Hibernate-reactive: colección perezosa buscando en Quarkus

Creado en 12 mar. 2021  ·  30Comentarios  ·  Fuente: hibernate/hibernate-reactive

Al intentar obtener una asociación de forma perezosa con Mutiny en Quarkus, no funciona si la búsqueda se realiza en una sesión diferente a la utilizada para conservar los datos. Si se usa la misma sesión (por lo que se obtiene justo después de persistir en la misma sesión), funciona.

Comportamiento esperado
Funciona también en diferentes sesiones.

Comportamiento real
Se lanza una excepción al intentar recuperar la asociación:

2021-03-12 09:56:23,634 ERROR [org.jbo.res.rea.com.cor.AbstractResteasyReactiveContext] (vert.x-eventloop-thread-2) Request failed: org.hibernate.LazyInitializationException: Collection cannot be initialized: com.example.Author.books
    at org.hibernate.reactive.session.impl.ReactiveSessionImpl.initializeCollection(ReactiveSessionImpl.java:330)
    at org.hibernate.collection.internal.AbstractPersistentCollection$4.doWork(AbstractPersistentCollection.java:589)
    at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:264)
    at org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:585)
    at org.hibernate.collection.internal.AbstractPersistentCollection.read(AbstractPersistentCollection.java:149)
    at org.hibernate.collection.internal.AbstractPersistentCollection$1.doWork(AbstractPersistentCollection.java:178)
    at org.hibernate.collection.internal.AbstractPersistentCollection$1.doWork(AbstractPersistentCollection.java:163)
    at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:264)
    at org.hibernate.collection.internal.AbstractPersistentCollection.readSize(AbstractPersistentCollection.java:162)
    at org.hibernate.collection.internal.PersistentBag.size(PersistentBag.java:371)
    at org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor.takeCollectionSizeSnapshot(LazyAttributeLoadingInterceptor.java:160)
    at org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor.lambda$loadAttribute$0(LazyAttributeLoadingInterceptor.java:110)
    at org.hibernate.bytecode.enhance.spi.interceptor.EnhancementHelper.performWork(EnhancementHelper.java:130)
    at org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor.loadAttribute(LazyAttributeLoadingInterceptor.java:76)
    at org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor.fetchAttribute(LazyAttributeLoadingInterceptor.java:72)
    at org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor.handleRead(LazyAttributeLoadingInterceptor.java:53)
    at org.hibernate.bytecode.enhance.spi.interceptor.AbstractInterceptor.readObject(AbstractInterceptor.java:153)
    at com.example.Author.$$_hibernate_read_books(Author.java)
    at com.example.Author.getBooks(Author.java:43)
    at com.example.TestResource.lambda$getBooks$2(TestResource.java:61)
    at io.smallrye.mutiny.operators.UniOnItemTransformToUni.invokeAndSubstitute(UniOnItemTransformToUni.java:31)
    at io.smallrye.mutiny.operators.UniOnItemTransformToUni$2.onItem(UniOnItemTransformToUni.java:74)
    at io.smallrye.mutiny.context.ContextPropagationUniInterceptor$1.lambda$onItem$1(ContextPropagationUniInterceptor.java:31)
    at io.smallrye.context.impl.wrappers.SlowContextualExecutor.execute(SlowContextualExecutor.java:19)
    at io.smallrye.mutiny.context.ContextPropagationUniInterceptor$1.onItem(ContextPropagationUniInterceptor.java:31)
    at io.smallrye.mutiny.operators.UniSerializedSubscriber.onItem(UniSerializedSubscriber.java:85)
    at io.smallrye.mutiny.context.ContextPropagationUniInterceptor$1.lambda$onItem$1(ContextPropagationUniInterceptor.java:31)
    at io.smallrye.context.impl.wrappers.SlowContextualExecutor.execute(SlowContextualExecutor.java:19)
    at io.smallrye.mutiny.context.ContextPropagationUniInterceptor$1.onItem(ContextPropagationUniInterceptor.java:31)
    at io.smallrye.mutiny.operators.UniDelegatingSubscriber.onItem(UniDelegatingSubscriber.java:24)
    at io.smallrye.mutiny.context.ContextPropagationUniInterceptor$1.lambda$onItem$1(ContextPropagationUniInterceptor.java:31)
    at io.smallrye.context.impl.wrappers.SlowContextualExecutor.execute(SlowContextualExecutor.java:19)
    at io.smallrye.mutiny.context.ContextPropagationUniInterceptor$1.onItem(ContextPropagationUniInterceptor.java:31)
    at io.smallrye.mutiny.operators.UniSerializedSubscriber.onItem(UniSerializedSubscriber.java:85)
    at io.smallrye.mutiny.context.ContextPropagationUniInterceptor$1.lambda$onItem$1(ContextPropagationUniInterceptor.java:31)
    at io.smallrye.context.impl.wrappers.SlowContextualExecutor.execute(SlowContextualExecutor.java:19)
    at io.smallrye.mutiny.context.ContextPropagationUniInterceptor$1.onItem(ContextPropagationUniInterceptor.java:31)
    at io.smallrye.mutiny.operators.uni.builders.UniCreateFromCompletionStage.lambda$forwardFromCompletionStage$1(UniCreateFromCompletionStage.java:30)
    at java.base/java.util.concurrent.CompletableFuture.uniWhenComplete(CompletableFuture.java:859)
    at java.base/java.util.concurrent.CompletableFuture$UniWhenComplete.tryFire(CompletableFuture.java:837)
    at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:506)
    at java.base/java.util.concurrent.CompletableFuture.complete(CompletableFuture.java:2073)
    at com.ibm.asyncutil.iteration.AsyncTrampoline$TrampolineInternal.unroll(AsyncTrampoline.java:127)
    at com.ibm.asyncutil.iteration.AsyncTrampoline$TrampolineInternal.lambda$unroll$0(AsyncTrampoline.java:123)
    at java.base/java.util.concurrent.CompletableFuture.uniWhenComplete(CompletableFuture.java:859)
    at java.base/java.util.concurrent.CompletableFuture$UniWhenComplete.tryFire(CompletableFuture.java:837)
    at java.base/java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:506)
    at java.base/java.util.concurrent.CompletableFuture.complete(CompletableFuture.java:2073)
    at org.hibernate.reactive.pool.impl.Handlers.lambda$toCompletionStage$0(Handlers.java:26)
    at io.vertx.sqlclient.impl.SqlResultHandler.complete(SqlResultHandler.java:98)
    at io.vertx.sqlclient.impl.SqlResultHandler.handle(SqlResultHandler.java:87)
    at io.vertx.sqlclient.impl.SqlResultHandler.handle(SqlResultHandler.java:33)
    at io.vertx.sqlclient.impl.SocketConnectionBase.handleMessage(SocketConnectionBase.java:241)
    at io.vertx.sqlclient.impl.SocketConnectionBase.lambda$init$0(SocketConnectionBase.java:88)
    at io.vertx.core.net.impl.NetSocketImpl.lambda$new$2(NetSocketImpl.java:101)
    at io.vertx.core.streams.impl.InboundBuffer.handleEvent(InboundBuffer.java:237)
    at io.vertx.core.streams.impl.InboundBuffer.write(InboundBuffer.java:127)
    at io.vertx.core.net.impl.NetSocketImpl.handleMessage(NetSocketImpl.java:357)
    at io.vertx.core.impl.ContextImpl.executeTask(ContextImpl.java:366)
    at io.vertx.core.impl.EventLoopContext.execute(EventLoopContext.java:43)
    at io.vertx.core.impl.ContextImpl.executeFromIO(ContextImpl.java:229)
    at io.vertx.core.net.impl.VertxHandler.channelRead(VertxHandler.java:163)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
    at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:436)
    at io.vertx.pgclient.impl.codec.PgEncoder.lambda$write$0(PgEncoder.java:78)
    at io.vertx.pgclient.impl.codec.PgCommandCodec.handleReadyForQuery(PgCommandCodec.java:138)
    at io.vertx.pgclient.impl.codec.PgDecoder.decodeReadyForQuery(PgDecoder.java:226)
    at io.vertx.pgclient.impl.codec.PgDecoder.channelRead(PgDecoder.java:86)
    at io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:251)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
    at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
    at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
    at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:163)
    at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:714)
    at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:650)
    at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:576)
    at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:493)
    at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:989)
    at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
    at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)

Reproductor

https://github.com/markusdlugi/hibernate-reactive-lazy-fetching

Pasos para reproducir el comportamiento:

  1. Inicie la base de datos usando infrastructure/docker-compose.yml .
  2. Inicie la aplicación Quarkus usando mvn quarkus:dev
  3. Enviar solicitud: POST http://localhost:8080/test/working .
    3.1. Esto funciona y devuelve una lista de libros.
  4. Enviar solicitud: GET http://localhost:8080/test/failing/1 (u otro ID de autor creado a través de POST http://localhost:8080/test/failing ).
    4.1 Esto falla con la excepción anterior.
  • Versión de Quarkus
  • Versión reactiva de Hibernate: 1.0.0.Beta4 (también probado con 1.0.0.CR1 sobrescribiendo la versión en Maven, mismo comportamiento)
  • Hibernate Core Version: 5.4.28.Final (también probado con 5.4.29.Final sobrescribiendo la versión en Maven, mismo comportamiento)
bug

Comentario más útil

Solo lo probé y puedo confirmar que la búsqueda diferida de colecciones ahora funciona en Quarkus. Gracias una vez más: sonríe:

Todos 30 comentarios

/ cc @DavideD @gavinking

Sí, creo que decidimos que esto era lo correcto: en JPA no existe la noción de reasociar una instancia de entidad con una sesión. Eso es un poco diferente al patrón de uso realmente antiguo de Hibernate, donde se podría usar saveOrUpdate() para reasociar directamente un objeto separado. En JPA, el modelo para tratar con objetos separados es la operación merge() .

(Y hay buenas razones para no tener esto. La experiencia es que, en la práctica, los usuarios se meten en líos con la reasociación).

Dado que cargar una colección separada realmente implica volver a adjuntar su padre, simplemente no admitimos esto.

Sin embargo, IIRC, yo le permiten hacer esto de una StatelessSession , ya que es mucho más compatible con el modelo de programación. Intente usar una sesión sin estado y avíseme si funciona para usted.

Hola @gavinking , gracias por la rápida respuesta.

No estoy muy seguro de entender lo que dice. Entonces, lo que implementé fue exactamente lo que se describe en la documentación :

session.find(Author.class, authorId)
    .chain(author -> Mutiny.fetch(author.getBooks()));

Esto funciona si el autor y sus libros se han mantenido con la misma sesión (es decir, en la misma solicitud HTTP), pero no funciona si uso otra sesión (es decir, otra solicitud).

Entonces, ¿está insinuando que este es un comportamiento previsto, al menos para una sesión "normal" (¿con estado?). Esto me parece un caso de uso realmente estándar, porque casi siempre intentaré obtener algunos datos de la base de datos algún tiempo después de persistirlos, es decir, en una sesión diferente. Esto significaría que al usar una sesión normal, las asociaciones diferidas no son compatibles. En ese caso, diría que debería mencionarse explícitamente en la documentación que se debe usar un StatelessSession si se van a buscar asociaciones perezosas.

Estoy diciendo que en la nueva sesión debe usar getReference() para obtener una referencia vinculada a la sesión de la entidad propietaria de la colección. Entonces puedes buscar la colección como quieras.

Este es el modelo JPA estándar, es incluso un poco más fácil de usar en recursos humanos porque sobrecargué getReference() .

@gavinking, ¿te refieres a algo como esto?

Author reference = session.getReference( Author.class, authorId );
return Mutiny.fetch( reference.getBooks() );

Claro, pero lo hice aún más fácil. Puede pasar directamente el autor separado a getReference() .

De acuerdo, probé esas sugerencias, pero desafortunadamente tampoco funcionaron. Tal vez pueda ser tan amable de señalar si estoy haciendo algo incorrecto aquí:

https://github.com/markusdlugi/hibernate-reactive-lazy-fetching/blob/f3458eee969d7c3597560aaf8a646b7aad2ee9b0/src/main/java/com/example/TestResource.java#L55 -L79

El enfoque que usa getReference() falla con la siguiente excepción:

2021-03-12 14:17:51,381 ERROR [org.jbo.res.rea.ser.cor.ExceptionMapping] (vert.x-eventloop-thread-10) Request failed : java.lang.NullPointerException
    at org.hibernate.engine.internal.AbstractEntityEntry.overwriteLoadedStateCollectionValue(AbstractEntityEntry.java:336)
    at org.hibernate.persister.entity.AbstractEntityPersister.initializeLazyProperty(AbstractEntityPersister.java:1144)
    at org.hibernate.persister.entity.AbstractEntityPersister.initializeEnhancedEntityUsedAsProxy(AbstractEntityPersister.java:4497)
    at org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor.forceInitialize(EnhancementAsProxyLazinessInterceptor.java:221)
    at org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor.lambda$handleRead$0(EnhancementAsProxyLazinessInterceptor.java:133)
    at org.hibernate.bytecode.enhance.spi.interceptor.EnhancementHelper.performWork(EnhancementHelper.java:130)
    at org.hibernate.bytecode.enhance.spi.interceptor.EnhancementAsProxyLazinessInterceptor.handleRead(EnhancementAsProxyLazinessInterceptor.java:98)
    at org.hibernate.bytecode.enhance.spi.interceptor.AbstractInterceptor.readObject(AbstractInterceptor.java:153)
    at com.example.Author.$$_hibernate_read_books(Author.java)
    at com.example.Author.getBooks(Author.java:43)
    at com.example.TestResource.getBooksUsingReference(TestResource.java:69)
    ...

La otra solución sugerida que usa StatelessSession falla con:

2021-03-12 14:21:11,840 ERROR [org.jbo.res.rea.ser.cor.ExceptionMapping] (vert.x-eventloop-thread-5) Request failed : org.hibernate.LazyInitializationException: Unable to perform requested lazy initialization [com.example.Author.books] - no session and settings disallow loading outside the Session
    at org.hibernate.bytecode.enhance.spi.interceptor.EnhancementHelper.throwLazyInitializationException(EnhancementHelper.java:199)
    at org.hibernate.bytecode.enhance.spi.interceptor.EnhancementHelper.performWork(EnhancementHelper.java:89)
    at org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor.loadAttribute(LazyAttributeLoadingInterceptor.java:76)
    at org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor.fetchAttribute(LazyAttributeLoadingInterceptor.java:72)
    at org.hibernate.bytecode.enhance.spi.interceptor.LazyAttributeLoadingInterceptor.handleRead(LazyAttributeLoadingInterceptor.java:53)
    at org.hibernate.bytecode.enhance.spi.interceptor.AbstractInterceptor.readObject(AbstractInterceptor.java:153)
    at com.example.Author.$$_hibernate_read_books(Author.java)
    at com.example.Author.getBooks(Author.java:43)
    at com.example.TestResource.lambda$getBooksUsingStatelessSession$3(TestResource.java:78)
    ...

Voy a echar un vistazo, parece relacionado con las mejoras del código de bytes.

@DavideD , ¿alguna actualización?

Intenté reproducir el problema sin Quarkus simplemente usando session-example en este repositorio, pero no pude hacerlo. Con solo Hibernate Reactive y simple Mutiny, funciona como se esperaba. También funciona usando find() , no es necesario getReference() o StatelessSession .

Entonces, este problema solo parece ocurrir en Quarkus.

Todavía lo estoy investigando.

El problema ocurre debido a las mejoras del código de bytes que están habilitadas de forma predeterminada en Quarkus.
Todavía no he descubierto qué conjunto de propiedades recrean el problema cuando solo uso Hibernate Reactive. Pero seguiré trabajando en eso hoy.

Ahora puedo recrear el error en reactivo. Requiere dos cosas:

  1. establecer hibernate.bytecode.allow_enhancement_as_proxy en verdadero
  2. establezca SessionFactoryOptions#enableCollectionInDefaultFetchGroup en falso. Debe establecerse en verdadero: https://github.com/hibernate/hibernate-reactive/blob/a3f4c61eef71baab8ca321da5af0bfb9052bde4b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/sersion/Fa.jactiveSesilder # L27

@gavinking ¿Estoy en lo cierto al pensar que deberíamos asegurarnos de que en Quarkus enableCollectionInDefaultFetchGroup esté configurado como falso? Admito que por el momento no entiendo completamente todo el problema (he intentado seguir la conversación sobre el número 374)

Um. Creo que solo tiene sentido tener la colección en el grupo de recuperación predeterminado. No creo que nunca tenga sentido excluirlo.

n EnableCollectionInDefaultFetchGroup de Quarkus se establece en false

Lo siento, sí, quería escribir is set to true . Tiempo de un descanso :-)

Ah, bueno, bueno :-) Tómate un descanso :-)

@DavideD Eso es un gran descubrimiento, gracias :)

Solo quería señalar, que de acuerdo con la discusión en # 374 y el hibernate / hibernate-orm # 3558 relacionado, parece que enableCollectionInDefaultFetchGroup se dejó deliberadamente como false en Quarkus. Al menos eso es lo que estoy recogiendo de este comentario:

https://github.com/hibernate/hibernate-orm/pull/3558#issuecomment -695003875

De todos modos, parece que Quarkus actualmente no establece esa propiedad en true , porque no está configurada en FastBootReactiveEntityManagerFactoryBuilder :

https://github.com/quarkusio/quarkus/blob/55cf15173ff7b2985a8422de050d1c9c708be57c/extensions/hibernate-reactive/runtime/src/main/java/io/quarkus/hibernate/reactive/runtime/boot/FastityBootReactory26

Y si lo verifiqué correctamente, el valor predeterminado en Hibernate ORM todavía parece ser false , lo que explicaría por qué vemos este comportamiento solo en Quarkus.

@Sanne necesita mirar este.

Si solo tenemos que configurarlo como verdadero, parece bastante fácil de resolver. Crearé una solución y luego verificaré con

Bueno, lo que me pregunto es si está configurado en falso porque eso es lo que necesita ORM, y si debemos tener cuidado de no pisar Hibernate normal.

gran descubrimiento, gracias. Sí, es un poco complicado porque las dos extensiones ("regular" y "reactiva") son cajeros automáticos altamente acoplados.

Es necesario separarlos correctamente, eso se desarrolló con demasiada prisa.

(OTOH, no estoy seguro de por qué ORM lo necesitaría; como he argumentado antes, creo que debería estar activado de forma predeterminada).

Hum OK. Hagamos esto para ambos ORM (en Quarkus).

@gavinking , creo que ese título está mal, ¿no? Supongo que querías poner enableCollectionInDefaultFetchGroup allí en su lugar. Las mejoras de código de bytes son necesarias para Quarkus de todos modos debido al nativo, supongo :)

Ah sí, claro, supongo que tienes razón.

¿Está diciendo que ve este error con la configuración predeterminada en Quarkus?

Sí, no se necesita ninguna configuración especial en Quarkus para obtener este error. @DavideD solo tuvo que hacer una configuración especial para reproducir en HR simple (sin Quarkus).

Seguro.

La cuestión es que en realidad no es un error de recursos humanos. Deliberadamente agregué esa "colección en el grupo de recuperación predeterminado" al núcleo específicamente porque lo necesitamos todo el tiempo en Recursos Humanos.

Entonces, el error está en la extensión de Quarkus que lo apaga.

(O, posiblemente, en core por hacer algo incorrecto de forma predeterminada).

Envié una solución para Quarkus: https://github.com/quarkusio/quarkus/pull/15818
getReference no parece funcionar en quarkus en este momento. Crearé un problema separado para él.

¡Suena genial, gracias! 👍

Solo lo probé y puedo confirmar que la búsqueda diferida de colecciones ahora funciona en Quarkus. Gracias una vez más: sonríe:

Gracias @markusdlugi

Gracias por denunciarlo @markusdlugi

¿Fue útil esta página
0 / 5 - 0 calificaciones