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。
  • Hibernate Reactiveバージョン: 1.0.0.Beta4(Mavenでバージョンを上書きすることにより、1.0.0.CR1でもテストされています。同じ動作です)
  • Hibernateコアバージョン: 5.4.28.Final(Mavenでバージョンを上書きすることで5.4.29.Finalでもテスト済み、同じ動作)
bug

最も参考になるコメント

テストしたところ、怠惰なコレクションのフェッチがQuarkusで機能することを確認できます。 もう一度ありがとう:smile:

全てのコメント30件

/ cc @DavideD @gavinking

ええ、私たちはこれが正しいことだと判断したと思います。JPAには、エンティティインスタンスをセッションに再関連付けするという概念はありません。 これは、 saveOrUpdate()を使用して分離されたオブジェクトを直接再関連付けできるHibernateの非常に古い使用パターンとは少し異なります。 JPAでは、分離オブジェクトを処理するためのモデルはmerge()操作です。

(そして、これを持たないのには十分な理由があります。実際には、ユーザーは再関連付けで混乱することになります。)

デタッチされたコレクションをロードすることは、実際にはその親を再アタッチすることを意味するため、これはサポートされていません。

ただし、IIRCは、そのプログラミングモデルとの互換性がはるかに高いため、 StatelessSessionでこれを実行できます。 ステートレスセッションを使用してみて、それが機能するかどうかを知らせてください。

こんにちは@gavinking 、迅速な返信をありがとう。

私はあなたが言っていることを私が得ているかどうかはよくわかりません。 したがって、私が実装したのは、ドキュメントに記載されて

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

これは、著者と彼の本が同じセッション(つまり、同じHTTPリクエスト)で永続化されている場合は機能しますが、別のセッション(つまり、別のリクエスト)を使用する場合は機能しません。

つまり、少なくとも「通常の」(ステートフル?)セッションでは、これが意図された動作であることを意味しているのでしょうか。 これは本当に標準的なユースケースのように思えます。なぜなら、ほとんどの場合、データベースを永続化した後、別のセッションでデータベースからデータを取得しようとするからです。 これは、通常のセッションを使用すると、レイジーアソシエーションがサポートされないことを意味します。 その場合、レイジーアソシエーションをフェッチする場合はStatelessSessionを使用する必要があることを、ドキュメントに明示的に記載する必要があると主張します。

新しいセッションでは、 getReference()を使用して、コレクションを所有するエンティティへのセッションバインドされた参照を取得する必要があると言っています。 次に、コレクションを好きなようにフェッチできます。

これは標準のJPAモデルであり、 getReference()をオーバーロードしたため、HRでの使用が少し簡単になりました。

@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 、更新はありますか?

このリポジトリでsession-exampleを使用するだけで、Quarkusなしで問題を再現しようとしましたが、再現できませんでした。 Hibernate ReactiveとプレーンなMutinyだけで、期待どおりに機能します。 また、 find()を使用して動作し、 getReference()またはStatelessSession必要ありません。

したがって、この問題はQuarkusでのみ発生するようです。

私はまだそれを調べています。

この問題は、Quarkusでデフォルトで有効になっているバイトコードの拡張が原因で発生します。
Hibernate Reactiveのみを使用した場合に、どのプロパティのセットが問題を再現するかはまだわかりません。 しかし、私は今日もそれに取り組み続けます

これで、リアクティブでエラーを再現できます。 それには2つのことが必要です。

  1. hibernate.bytecode.allow_enhancement_as_proxyをtrueに設定します
  2. SessionFactoryOptions#enableCollectionInDefaultFetchGroupをfalseに設定します。 trueに設定する必要があります: https

@gavinking QuarkusでenableCollectionInDefaultFetchGroupがfalseに設定されていることを確認する必要があると考えるのは正しいですか? 現時点では、問題全体を完全には理解していないことを認めます(問題#374の会話をフォローしようとしました)

ええとコレクションをデフォルトのフェッチグループに含めることだけが理にかなっていると思います。 私はそれが今までそれを除外することに意味がないと思います。

n QuarkusenableCollectionInDefaultFetchGroupがfalseに設定されている

申し訳ありませんが、ええ、私はis set to trueを書くつもり

ああOKいいね:-)休憩してください:-)

@DavideDそれはいくつかの素晴らしい発見です、ありがとう:)

指摘したいのですが、#374および関連するhibernate / hibernate-orm#3558での議論によると、QuarkusではenableCollectionInDefaultFetchGroupが意図的にfalseになっているようです。 少なくともそれは私がこのコメントから集めているものです:

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/boot/FastBootReactiveEntityManagerFactoryBuilder.java#L26 -L32

そして、正しくチェックした場合、HibernateORMのデフォルトはまだfalse 。これは、Quarkusでのみこの動作が見られる理由を説明しています。

@Sanneはこれを見る必要があります。

trueに設定するだけでよい場合、解決するのは非常に簡単なようです。 修正を作成してから@Sanneに確認し

さて、私が疑問に思っているのは、それがORMに必要なものであるため、falseに設定されているかどうか、そして通常のHibernateを踏まないように注意する必要があるかどうかです。

素晴らしい発見、ありがとう。 はい、2つの拡張機能(「通常」と「リアクティブ」)は高度に結合されたATMであるため、少し注意が必要です。

それらを適切に分離する必要があります、それはあまりにも急いで開発されました。

(OTOH、なぜORMがそれを必要とするのかわかりません。前に議論したように、デフォルトでオンになっているはずです。)

ハムOK。 (Quarkusの)両方のORMに対してこれを実行しましょう。

@gavinking 、タイトルが間違っていると思いますよね? 代わりにenableCollectionInDefaultFetchGroupを置くつもりだったと思います。 とにかくネイティブのため、Quarkusにはバイトコードの拡張が必要だと思います:)

そうそう、確かに、私はあなたが正しいと思います。

Quarkusのデフォルト設定でこのバグが発生したと言っていますか?

はい、このバグを取得するためにQuarkusで特別な構成は必要ありません。 @DavideDは、プレーンHR(Quarkusなし)で再現するために特別な構成を行う必要がありました。

もちろん。

問題は、それは実際にはHRのバグではないということです。 その「デフォルトのフェッチグループのコレクション」をコアに意図的に追加しました。これは、HRで常に必要になるためです。

したがって、バグはQuarkus拡張機能にあり、それをオフにします。

(または、間違いなく、デフォルトで間違ったことを行うためのコアです。)

Quarkusの修正を送信しました: https
getReferenceは、現時点ではクォークで機能していないようです。 別の問題を作成します。

いいですね、ありがとう! 👍

テストしたところ、怠惰なコレクションのフェッチがQuarkusで機能することを確認できます。 もう一度ありがとう:smile:

ありがとう@markusdlugi

報告してくれてありがとう@markusdlugi

このページは役に立ちましたか?
0 / 5 - 0 評価

関連する問題

Xset-s picture Xset-s  ·  3コメント

gavinking picture gavinking  ·  16コメント

hantsy picture hantsy  ·  7コメント

akoufa picture akoufa  ·  30コメント

pqab picture pqab  ·  21コメント