Hibernate-reactive: Quarkus 中的惰性集合获取

创建于 2021-03-12  ·  30评论  ·  资料来源: hibernate/hibernate-reactive

当试图在 Quarkus 中使用 Mutiny 懒惰地获取关联时,如果获取是在与用于持久化数据的会话不同的会话中执行的,则它不起作用。 如果使用同一个会话(所以在同一个会话中持久化后立即获取),它就可以工作。

预期行为
它也适用于不同的会话。

实际行为
尝试获取关联时抛出异常:

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)

再现者

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

重现行为的步骤:

  1. 使用infrastructure/docker-compose.yml启动数据库。
  2. 使用mvn quarkus:dev启动 Quarkus 应用程序
  3. 发送请求: POST http://localhost:8080/test/working
    3.1. 这有效并返回书籍列表。
  4. 发送请求: GET http://localhost:8080/test/failing/1 (或通过POST http://localhost:8080/test/failing创建的其他 authorId)。
    4.1 除上述例外情况外,这将失败。
  • Quarkus 版本: 1.12.2.Final
  • Hibernate Reactive 版本: 1.0.0.Beta4(也在 1.0.0.CR1 上通过覆盖 Maven 中的版本进行测试,行为相同)
  • Hibernate 核心版本: 5.4.28.Final(也在 5.4.29.Final 中通过覆盖 Maven 中的版本进行测试,行为相同)

最有用的评论

刚刚对其进行了测试,可以确认惰性集合获取现在可以在 Quarkus 中使用。 再次感谢:smile:

所有30条评论

/cc @DavideD @gavinking

是的,我认为我们认为这是正确的做法:在 JPA 中没有将实体实例与会话重新关联的概念。 这与真正古老的 Hibernate 使用模式有点不同,在那里您可以使用saveOrUpdate()直接重新关联分离的对象。 在 JPA 中,处理分离对象的模型是merge()操作。

(并且有充分的理由不这样做。经验是,在实践中,用户会因重新关联而陷入混乱。)

由于加载一个分离的集合实际上意味着重新附加它的父集合,我们根本不支持这一点。

但是,IIRC,我确实让您在StatelessSession执行此操作,因为它与该编程模型更加兼容。 尝试使用无状态会话,让我知道它是否适合您。

@gavinking ,感谢您的快速回复。

我不太确定我是否明白你在说什么。 所以我实现的正是文档中描述的:

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

如果作者和他的书一直使用同一个会话(即,在同一个 HTTP 请求中),这是有效的,但如果我使用另一个会话(即另一个请求),它就不起作用。

所以你暗示这是预期的行为,至少对于“正常”(有状态?)会话? 这对我来说似乎是一个非常标准的用例,因为我几乎总是会在持久化数据库一段时间后尝试从数据库中获取一些数据,所以在不同的会话中。 这意味着使用普通会话时,不支持惰性关联。 在这种情况下,我认为应该在文档中明确提到如果要获取惰性关联,则需要使用StatelessSession

我是说在新会话中,您应该使用getReference()来获取对拥有该集合的实体的会话绑定引用。 然后你可以随心所欲地获取集合。

这是标准的 JPA 模型,在 HR 中使用起来更容易一些,因为我重载了getReference()

@gavinking你的意思是这样的吗?

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

当然,但我让它变得更容易了。 您可以直接将分离的作者传递给getReference()

好的,我尝试了这些建议,但不幸的是它们也不起作用。 也许你可以很友好地指出我在这里做错了什么:

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

使用getReference()失败,但出现以下异常:

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)
    ...

使用StatelessSession的另一个建议解决方案失败:

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)
    ...

我去看看,好像和字节码增强有关

@DavideD ,有更新吗?

我试图通过在这个 repo 中使用session-example在没有 Quarkus 的情况下重现这个问题,但我无法这样做。 仅使用 Hibernate Reactive 和普通 Mutiny,它就能按预期工作。 它也适用于find() ,不需要getReference()StatelessSession

所以这个问题似乎只发生在 Quarkus 中。

我还在研究它。

问题的发生是因为 Quarkus 中默认启用了字节码增强功能。
当仅使用 Hibernate Reactive 时,我还没有弄清楚哪一组属性会重现问题。 但我今天会继续努力

我现在可以在反应式中重新创建错误。 它需要两件事:

  1. hibernate.bytecode.allow_enhancement_as_proxy为 true
  2. SessionFactoryOptions#enableCollectionInDefaultFetchGroup为 false。 它应该设置为 true: https :

@gavinking我认为我们应该确保在 Quarkus 中将enableCollectionInDefaultFetchGroup设置为 false 是否正确? 我承认目前我不完全理解整个问题(我已经尝试关注关于问题 #374 的对话)

嗯。 我认为只有在默认获取组中包含集合才有意义。 我认为排除它没有任何意义。

n Quarkus enableCollectionInDefaultFetchGroup 设置为 false

抱歉,是的,我的意思是写is set to true 。 是时候休息了:-)

嗯,好的 :-) 休息一下 :-)

@DavideD这是一些很棒的发现,谢谢:)

只是想指出,根据 #374 和相关的 hibernate/hibernate-orm#3558 中的讨论,似乎enableCollectionInDefaultFetchGroup false在 Quarkus 中被故意留下为

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

无论如何,看起来 Quarkus 目前没有将该属性设置为true ,因为它在FastBootReactiveEntityManagerFactoryBuilder未配置:

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

如果我检查正确,Hibernate ORM 中的默认值似乎仍然是false ,这可以解释为什么我们只在 Quarkus 中看到这种行为。

@Sanne需要看看这个。

如果我们只需要将它设置为 true ,似乎很容易解决。 我将创建一个修复程序,然后与@Sanne 核对

那么我想知道的是它是否设置为 false 因为这是 ORM 所需要的,并且我们是否需要小心不要踩到常规的 Hibernate?

很棒的发现,谢谢。 是的,这有点棘手,因为这两个扩展(“常规”和“反应性”)是高度耦合的 ATM。

需要适当地将它们分开,这是开发得太匆忙了。

(OTOH,我不知道为什么 ORM 需要它;正如我之前所说的,我认为它应该默认开启。)

嗯嗯。 让我们为两个 ORM(在 Quarkus 中)执行此操作。

@gavinking ,我认为标题是错误的,不是吗? 我猜你是想把enableCollectionInDefaultFetchGroup放在那里。 无论如何,由于本机,Quarkus 需要字节码增强,我假设:)

啊,是的,当然,我想你是对的。

你是说你在 Quarkus 中看到这个带有默认设置的错误?

是的,在 Quarkus 中无需特殊配置即可获得此错误。 @DavideD只需要做一些特殊的配置就可以在普通 HR(没有 Quarkus)中重现。

当然。

问题是它实际上不是 HR 中的错​​误。 我特意将“默认获取组中的集合”添加到核心中,因为我们在 HR 中一直需要它。

所以这个错误是在 Quarkus 扩展中,它关闭了它。

(或者,可以说,在默认情况下做错事的核心。)

我已经发送了 Quarkus 的修复程序: https :
getReference目前似乎不适用于 quarkus。 我将为它创建一个单独的问题。

太好听了,谢谢! 👍

刚刚对其进行了测试,可以确认惰性集合获取现在可以在 Quarkus 中使用。 再次感谢:smile:

谢谢@markusdlugi

感谢您报告@markusdlugi

此页面是否有帮助?
0 / 5 - 0 等级

相关问题

gavinking picture gavinking  ·  6评论

DavideD picture DavideD  ·  31评论

Thomodachi picture Thomodachi  ·  15评论

DavideD picture DavideD  ·  37评论

yaakov-berkovitch picture yaakov-berkovitch  ·  16评论