Wenn versucht wird, eine Assoziation mit Mutiny in Quarkus trĂ€ge abzurufen, funktioniert es nicht, wenn der Abruf in einer anderen Sitzung als derjenigen ausgefĂŒhrt wird, die zum Beibehalten der Daten verwendet wurde. Wenn dieselbe Sitzung verwendet wird (also das Abrufen direkt nach dem Beibehalten in derselben Sitzung), funktioniert es.
Erwartetes Verhalten
Es funktioniert auch in verschiedenen Sitzungen.
TatsÀchliches Verhalten
Beim Versuch, die Assoziation abzurufen, wird eine Ausnahme ausgelöst:
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)
WiedergabegerÀt
https://github.com/markusdulgi/hibernate-reactive-lazy-fetching
Schritte zum Reproduzieren des Verhaltens:
infrastructure/docker-compose.yml
.mvn quarkus:dev
POST http://localhost:8080/test/working
.GET http://localhost:8080/test/failing/1
(oder eine andere Autoren-ID, die ĂŒber POST http://localhost:8080/test/failing
)./cc @DavideD @gavinking
Ja, ich denke, wir haben entschieden, dass dies das Richtige war: In JPA gibt es keine Vorstellung davon, eine EntitÀtsinstanz einer Sitzung erneut zuzuordnen. Das ist etwas anders als das wirklich alte Verwendungsmuster von Hibernate, bei dem Sie saveOrUpdate()
könnten, um ein getrenntes Objekt direkt wieder zuzuordnen. In JPA ist das Modell fĂŒr den Umgang mit abgetrennten Objekten die Operation merge()
.
(Und es gibt gute GrĂŒnde, dies nicht zu tun. Die Erfahrung zeigt, dass Benutzer in der Praxis mit der Reassoziation selbst ins Schlamassel geraten.)
Da das Laden einer getrennten Sammlung wirklich das erneute AnhĂ€ngen der ĂŒbergeordneten Sammlung erfordert, unterstĂŒtzen wir dies einfach nicht.
Allerdings IIRC, ich lasse Sie dies tun , in einem StatelessSession
, da es viel mehr kompatibel mit dem Programmiermodell ist. Versuchen Sie es mit einer zustandslosen Sitzung und lassen Sie mich wissen, ob es fĂŒr Sie funktioniert.
Hallo @gavinking , danke fĂŒr die schnelle Antwort.
Ich bin mir nicht ganz sicher, ob ich verstehe, was Sie sagen. Was ich also implementiert habe, war genau das, was in der Dokumentation beschrieben ist :
session.find(Author.class, authorId)
.chain(author -> Mutiny.fetch(author.getBooks()));
Dies funktioniert, wenn der Autor und seine BĂŒcher in derselben Sitzung (dh in derselben HTTP-Anfrage) gespeichert wurden, aber es funktioniert nicht, wenn ich eine andere Sitzung (dh eine andere Anfrage) verwende.
Sie implizieren also, dass dies beabsichtigt ist, zumindest fĂŒr eine "normale" (zustandsbehaftete?) Sitzung? Dies scheint mir ein wirklich ĂŒblicher Anwendungsfall zu sein, da ich fast immer versuchen werde, einige Zeit nach dem Persistenz, also in einer anderen Sitzung, einige Daten aus der Datenbank zu holen. Dies wĂŒrde bedeuten, dass bei einer normalen Sitzung Lazy-Assoziationen nicht unterstĂŒtzt werden. In diesem Fall wĂŒrde ich argumentieren, dass in der Dokumentation explizit erwĂ€hnt werden sollte, dass ein StatelessSession
muss, wenn faule Assoziationen abgerufen werden sollen.
Ich sage, dass Sie in der neuen Sitzung getReference()
, um einen sitzungsgebundenen Verweis auf die EntitÀt zu erhalten, die die Sammlung besitzt. Dann können Sie die Sammlung nach Belieben abholen.
Dies ist das Standard-JPA-Modell, es ist sogar noch ein bisschen einfacher in der Personalabteilung zu verwenden, weil ich getReference()
ĂŒberladen habe.
@gavinking meinst du sowas?
Author reference = session.getReference( Author.class, authorId );
return Mutiny.fetch( reference.getBooks() );
Sicher, aber ich habe es noch einfacher gemacht. Sie können den abgetrennten Autor direkt an getReference()
.
Okay, ich habe die VorschlÀge ausprobiert, aber leider haben sie auch nicht funktioniert. Vielleicht können Sie so nett sein, darauf hinzuweisen, wenn ich hier etwas falsch mache:
Der Ansatz mit getReference()
schlÀgt mit folgender Ausnahme fehl:
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)
...
Die andere vorgeschlagene Lösung mit StatelessSession
schlÀgt fehl mit:
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)
...
Ich werde es mir ansehen, es scheint mit Bytecode-Erweiterungen zu tun zu haben
@DavideD , irgendein Update?
Ich habe versucht, das Problem ohne Quarkus zu reproduzieren, indem ich nur session-example
in diesem Repository verwendet habe, aber es war mir nicht möglich. Mit nur Hibernate Reactive und einfachem Mutiny funktioniert es wie erwartet. Es funktioniert auch mit find()
, es ist kein getReference()
oder StatelessSession
erforderlich.
Dieses Problem scheint also nur in Quarkus aufzutreten.
Ich suche noch nach.
Das Problem tritt aufgrund von Bytecode-Erweiterungen auf, die standardmĂ€Ăig in Quarkus aktiviert sind.
Ich habe immer noch nicht herausgefunden, welche Eigenschaften das Problem wiederherstellen, wenn nur Hibernate Reactive verwendet wird. Aber ich werde heute weiter daran arbeiten
Ich kann den Fehler jetzt reaktiv reproduzieren. Es erfordert zwei Dinge:
hibernate.bytecode.allow_enhancement_as_proxy
auf trueSessionFactoryOptions#enableCollectionInDefaultFetchGroup
auf false. Es sollte auf true gesetzt werden: https://github.com/hibernate/hibernate-reactive/blob/a3f4c61eef71baab8ca321da5af0bfb9052bde4b/hibernate-reactive-core/src/main/java/org/hibernate/reactive/provider/service/ReactiveService.java .Builder #L27@gavinking Liege ich richtig, enableCollectionInDefaultFetchGroup
auf false gesetzt ist? Ich gebe zu, dass ich im Moment das ganze Thema nicht ganz verstehe (ich habe versucht, dem GesprÀch zu Thema #374 zu folgen)
Ăh. Ich denke, es macht nur Sinn, die Sammlung in der Standardabrufgruppe zu haben. Ich glaube nicht, dass es jemals Sinn macht, es auszuschlieĂen.
n Quarkus enableCollectionInDefaultFetchGroup ist auf false gesetzt
Entschuldigung, ja, ich wollte is set to true
schreiben. Zeit fĂŒr eine Pause :-)
Ah OK gut :-) Mach eine Pause :-)
@DavideD Das sind tolle Erkenntnisse, danke :)
Wollte nur darauf hinweisen, dass gemÀà der Diskussion in #374 und dem dazugehörigen hibernate/hibernate-orm#3558 anscheinend enableCollectionInDefaultFetchGroup
false
in Quarkus absichtlich auf
https://github.com/hibernate/hibernate-orm/pull/3558#issuecomment -695003875
Wie auch immer, es sieht so aus, als wĂŒrde Quarkus diese Eigenschaft derzeit nicht auf true
, da sie in FastBootReactiveEntityManagerFactoryBuilder
konfiguriert ist:
Und wenn ich es richtig ĂŒberprĂŒft habe, scheint die Standardeinstellung in Hibernate ORM immer noch false
, was erklĂ€ren wĂŒrde, warum wir dieses Verhalten nur in Quarkus sehen.
@Sanne muss sich das ansehen.
Wenn wir es nur auf true setzen mĂŒssen, scheint es ziemlich einfach zu lösen. Ich werde einen Fix erstellen und dann bei @Sanne nachfragen
Nun, was ich mich frage ist, ob es auf false gesetzt ist, weil ORM das braucht, und ob wir vorsichtig sein mĂŒssen, nicht in den regulĂ€ren Ruhezustand zu treten?
toller Fund, danke. Ja, es ist ein wenig knifflig, da die beiden Nebenstellen ("regulÀr" und "reaktiv") hoch gekoppelt sind.
MĂŒssen sie richtig trennen, das wurde in zu viel Eile entwickelt.
(OTOH, ich bin mir nicht sicher, warum ORM es brauchen wĂŒrde; wie ich bereits argumentiert habe, sollte es meiner Meinung nach standardmĂ€Ăig aktiviert sein.)
HM ok. Lassen Sie uns dies fĂŒr beide ORMs (in Quarkus) tun.
@gavinking , ich denke, dieser Titel ist falsch, oder? Ich schÀtze, du wolltest stattdessen enableCollectionInDefaultFetchGroup
dort platzieren. Bytecode-Erweiterungen sind fĂŒr Quarkus sowieso wegen nativer notwendig, nehme ich an :)
Ah ja, klar, ich denke du hast recht.
Sie sagen, dass Sie diesen Fehler mit den Standardeinstellungen in Quarkus sehen?
Ja, in Quarkus ist keine spezielle Konfiguration erforderlich, um diesen Fehler zu beheben. @DavideD musste nur eine spezielle Konfiguration vornehmen, um im normalen HR (ohne Quarkus) zu reproduzieren.
Sicher.
Die Sache ist, dass es sich nicht wirklich um einen Fehler in der Personalabteilung handelt. Ich habe diese "Sammlung in der Standardabrufgruppe" absichtlich zum Kern hinzugefĂŒgt, weil wir sie im HR stĂ€ndig brauchen.
Der Fehler liegt also in der Quarkus-Erweiterung, die ihn ausschaltet.
(Oder wohl im Kern dafĂŒr, standardmĂ€Ăig das Falsche zu tun.)
Ich habe Quarkus einen Fix geschickt: https://github.com/quarkusio/quarkus/pull/15818
getReference
scheint im Moment bei Quarkus nicht zu funktionieren. Ich werde dafĂŒr eine separate Ausgabe erstellen.
Klingt super, danke! đ
Habe es gerade getestet und kann bestÀtigen, dass das faule Sammlungsabrufen jetzt in Quarkus funktioniert. Danke nochmal :smile:
Danke @markusdulgi
Danke, dass du es
Hilfreichster Kommentar
Habe es gerade getestet und kann bestÀtigen, dass das faule Sammlungsabrufen jetzt in Quarkus funktioniert. Danke nochmal :smile: