使用 Kotlin 的 spring-data-mongodb 上的 @Transient 在读取期间导致异常

Posted

技术标签:

【中文标题】使用 Kotlin 的 spring-data-mongodb 上的 @Transient 在读取期间导致异常【英文标题】:@Transient on spring-data-mongodb with Kotlin results in exception during read 【发布时间】:2019-06-06 07:53:51 【问题描述】:

我的项目中使用 Kotlin、SpringBoot 2.0 和 MongoDB(使用 Spring Data)和@Transient 的组合时遇到问题。首先,这是我声明数据类的方式

@Document data class Child(@Id val id: String?, val name: String)
@Document data class Parent(@Id val id: String?, val child: Child? = null, val childId: String)

我知道有更好的方法可以从 Parent 引用 Child,但 MongoDB 是现有的。

这里的问题是,我不想将child 属性保留在Parent 中,因此我尝试使用kotlin.jvm.Transientorg.springframework.data.annotation.Transient 或两者来注释该属性。但似乎没有任何组合可以解决我的问题。我面临的问题是:

    当我使用kotlin.jvm.Transient 时,它正在加载,但是当我将其保存回来时,它会将child 属性与它一起保留 当我使用org.springframework.data.annotation.Transient 时,无法通过堆栈跟踪读取:
org.springframework.data.mapping.MappingException: No property child found on entity class com.example.sample.domain.Parent to bind constructor parameter to!
    at org.springframework.data.mapping.model.PersistentEntityParameterValueProvider.getParameterValue(PersistentEntityParameterValueProvider.java:68)
    at org.springframework.data.mapping.model.SpELExpressionParameterValueProvider.getParameterValue(SpELExpressionParameterValueProvider.java:49)
    at org.springframework.data.convert.ClassGeneratingEntityInstantiator$EntityInstantiatorAdapter.extractInvocationArguments(ClassGeneratingEntityInstantiator.java:250)
    at org.springframework.data.convert.ClassGeneratingEntityInstantiator$EntityInstantiatorAdapter.createInstance(ClassGeneratingEntityInstantiator.java:223)
    at org.springframework.data.convert.ClassGeneratingEntityInstantiator.createInstance(ClassGeneratingEntityInstantiator.java:84)
    at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:272)
    at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:245)
    at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:194)
    at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:190)
    at org.springframework.data.mongodb.core.convert.MappingMongoConverter.read(MappingMongoConverter.java:78)
    at org.springframework.data.mongodb.core.ReactiveMongoTemplate$ReadDocumentCallback.doWith(ReactiveMongoTemplate.java:2920)
    at reactor.core.publisher.FluxMap$MapSubscriber.onNext(FluxMap.java:100)
    at reactor.core.publisher.MonoNext$NextSubscriber.onNext(MonoNext.java:76)
    at com.mongodb.reactivestreams.client.internal.ObservableToPublisher$1.onNext(ObservableToPublisher.java:68)
    at com.mongodb.async.client.AbstractSubscription.onNext(AbstractSubscription.java:135)
    at com.mongodb.async.client.AbstractSubscription.processResultsQueue(AbstractSubscription.java:203)
    at com.mongodb.async.client.AbstractSubscription.tryProcessResultsQueue(AbstractSubscription.java:159)
    at com.mongodb.async.client.SingleResultCallbackSubscription$1.onResult(SingleResultCallbackSubscription.java:48)
    at com.mongodb.async.client.FindIterableImpl$1$1.onResult(FindIterableImpl.java:213)
    at com.mongodb.async.client.FindIterableImpl$1$1.onResult(FindIterableImpl.java:204)
    at com.mongodb.operation.AsyncQueryBatchCursor.next(AsyncQueryBatchCursor.java:136)
    at com.mongodb.operation.AsyncQueryBatchCursor.next(AsyncQueryBatchCursor.java:100)
    at com.mongodb.async.client.FindIterableImpl$1.onResult(FindIterableImpl.java:204)
    at com.mongodb.async.client.FindIterableImpl$1.onResult(FindIterableImpl.java:198)
    at com.mongodb.internal.async.ErrorHandlingResultCallback.onResult(ErrorHandlingResultCallback.java:49)
    at com.mongodb.async.client.OperationExecutorImpl$1$1.onResult(OperationExecutorImpl.java:82)
    at com.mongodb.internal.async.ErrorHandlingResultCallback.onResult(ErrorHandlingResultCallback.java:49)
    at com.mongodb.operation.FindOperation$3.onResult(FindOperation.java:806)
    at com.mongodb.operation.OperationHelper$ReferenceCountedReleasingWrappedCallback.onResult(OperationHelper.java:364)
    at com.mongodb.operation.CommandOperationHelper$2.onResult(CommandOperationHelper.java:405)
    at com.mongodb.internal.async.ErrorHandlingResultCallback.onResult(ErrorHandlingResultCallback.java:49)
    at com.mongodb.internal.connection.DefaultServer$DefaultServerProtocolExecutor$2.onResult(DefaultServer.java:227)
    at com.mongodb.internal.async.ErrorHandlingResultCallback.onResult(ErrorHandlingResultCallback.java:49)
    at com.mongodb.internal.connection.CommandProtocolImpl$1.onResult(CommandProtocolImpl.java:85)
    at com.mongodb.internal.connection.DefaultConnectionPool$PooledConnection$1.onResult(DefaultConnectionPool.java:461)
    at com.mongodb.internal.connection.UsageTrackingInternalConnection$2.onResult(UsageTrackingInternalConnection.java:111)
    at com.mongodb.internal.async.ErrorHandlingResultCallback.onResult(ErrorHandlingResultCallback.java:49)
    at com.mongodb.internal.connection.InternalStreamConnection$2$1.onResult(InternalStreamConnection.java:379)
    at com.mongodb.internal.connection.InternalStreamConnection$2$1.onResult(InternalStreamConnection.java:356)
    at com.mongodb.internal.connection.InternalStreamConnection$MessageHeaderCallback$MessageCallback.onResult(InternalStreamConnection.java:651)
    at com.mongodb.internal.connection.InternalStreamConnection$MessageHeaderCallback$MessageCallback.onResult(InternalStreamConnection.java:618)
    at com.mongodb.internal.connection.InternalStreamConnection$5.completed(InternalStreamConnection.java:494)
    at com.mongodb.internal.connection.InternalStreamConnection$5.completed(InternalStreamConnection.java:491)
    at com.mongodb.connection.netty.NettyStream.readAsync(NettyStream.java:236)
    at com.mongodb.internal.connection.InternalStreamConnection.readAsync(InternalStreamConnection.java:491)
    at com.mongodb.internal.connection.InternalStreamConnection.access$1000(InternalStreamConnection.java:74)
    at com.mongodb.internal.connection.InternalStreamConnection$MessageHeaderCallback.onResult(InternalStreamConnection.java:608)
    at com.mongodb.internal.connection.InternalStreamConnection$MessageHeaderCallback.onResult(InternalStreamConnection.java:593)
    at com.mongodb.internal.connection.InternalStreamConnection$5.completed(InternalStreamConnection.java:494)
    at com.mongodb.internal.connection.InternalStreamConnection$5.completed(InternalStreamConnection.java:491)
    at com.mongodb.connection.netty.NettyStream.readAsync(NettyStream.java:236)
    at com.mongodb.connection.netty.NettyStream.handleReadResponse(NettyStream.java:266)
    at com.mongodb.connection.netty.NettyStream.access$600(NettyStream.java:66)
    at com.mongodb.connection.netty.NettyStream$InboundBufferHandler.channelRead0(NettyStream.java:325)
    at com.mongodb.connection.netty.NettyStream$InboundBufferHandler.channelRead0(NettyStream.java:322)
    at io.netty.channel.SimpleChannelInboundHandler.channelRead(SimpleChannelInboundHandler.java:105)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:340)
    at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1434)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:362)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:348)
    at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:965)
    at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:163)
    at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:648)
    at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:583)
    at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:500)
    at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:462)
    at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:897)
    at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
    at java.base/java.lang.Thread.run(Thread.java:844)

    当我同时使用这两个注释时,结果与 #1 相同(显然),因为应用程序在尝试执行某些逻辑之前正在读取数据。

非常感谢任何帮助

【问题讨论】:

【参考方案1】:

这可能很烦人,但它是弹簧数据的工作方式。

Spring数据从Mongo加载Parent数据后需要Child对象创建Parent对象,但Mongo中没有Child数据,抛出异常。

所以,我们应该先创建一个没有Child对象的Parent对象,然后再初始化Parent对象的child字段。

我认为使用@org.springframework.data.annotation.Transient 和@PersistenceConstructor 作为以下代码是最好的方法。

@Document data class Child(@Id val id: String?, val name: String)
@Document data class Parent(@Id val id: String?, @Transient val child: Child? = null, val childId: String) 
  @PersistenceConstructor   
  constructor(id: String, childId: String): this(id = id, childId = childId, child = null)

【讨论】:

【参考方案2】:

添加一个新的kotlin构造函数并给它注解@PersistenceConstructor

【讨论】:

我没有父子问题,但我确实有瞬态,这为我解决了问题。【参考方案3】:

由于您在构造函数中声明属性,我相信您需要执行以下操作才能将注释应用于隐式创建的字段,因为 Spring Data 将查看字段的注释而不是构造函数属性的注释:

@get:Transient val child: Child? = null

【讨论】:

还是一样。在所有 3 种情况下,错误保持不变。 :(@Document(collection = "parent") data class Parent(@Id val id: String? = null, @field:KotlinTransient @field:SpringTransient val child: Child? = null, val childId: String) @JonathanHandoyo 也可以尝试使用@get:Transient 而不是@field 仍然没有...我发现另一种有效的模式是在data class 的正文中将“瞬态”成员声明为vars,但由于.copy(...) 这并不直观方法不适用于类体内声明的成员,.equals(...) 不会考虑这些 我刚刚检查了使用一些不同注释生成的字节码,在我看来@get:org.springframework.data.annotation.Transient val child 应该在构造函数中工作。字节码中的 getter 方法与我在类主体中使用 @Transient 注释显式定义的方法具有相同的注释。

以上是关于使用 Kotlin 的 spring-data-mongodb 上的 @Transient 在读取期间导致异常的主要内容,如果未能解决你的问题,请参考以下文章

Why Kotlin

Why Kotlin

使用 Kotlin 提高生产力

Kotlin 协程协程底层实现 ① ( Kotlin 协程分层架构 | 基础设施层 | 业务框架层 | 使用 Kotlin 协程基础设施层标准库 Api 实现协程 )

Kotlin 协程协程底层实现 ① ( Kotlin 协程分层架构 | 基础设施层 | 业务框架层 | 使用 Kotlin 协程基础设施层标准库 Api 实现协程 )

Kotlin初识Kotlin