@Transient on spring-data-mongodb with Kotlin results in exception during read

Viewed 1566

I am having a problem in my project with a combination of Kotlin, SpringBoot 2.0, and MongoDB (with Spring Data), with @Transient. First of all, here is how I declared my data classes

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

I understand there could be a better way to refer to the Child from the Parent, but the MongoDB is an existing one.

The problem here is, I don't want to persist the child property inside Parent, so I tried to annotate the property with kotlin.jvm.Transient, or org.springframework.data.annotation.Transient, or both. But no combination seems to solve my problem. The problem that I am facing is:

  1. When I am using kotlin.jvm.Transient, it is loading, but when I save it back, it will persist the child property together with it
  2. When I am using org.springframework.data.annotation.Transient, it is unable to read with stack trace:
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. When I am using both annotations, it is the same result as #1 (obviously), since the application is reading the data before trying to do some logic.

Any help is greatly appreciated

3 Answers

Add a new kotlin constructor and give it the annotation @PersistenceConstructor

Since you are declaring the property in the constructor, I believe you need to do the following to apply the annotation to the field that is implicitly created, since Spring Data will be looking at the field's annotations instead of the constructor property's annotations:

@get:Transient val child: Child? = null

It could be very annoying, but it is very exact way how spring data works.

Spring data needs a Child object to create a Parent object after loading a Parent data from Mongo, but there is no Child data in Mongo, and the exception is thrown.

So, we should create a Parent object without a Child object first, and then initialize the child field of the Parent object.

I think using @org.springframework.data.annotation.Transient and @PersistenceConstructor as following code is the best way to do it.

@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)
}
Related