Search code examples
javaspringmongodbspring-mongodb

Error when save Object - Optimistic lock exception on saving entity


I just doing simple transactions like :

  1. findByVariable(String variable);
  2. manipulate the data, then
  3. save(data);

but i got Exception like this

org.springframework.dao.OptimisticLockingFailureException: Optimistic lock exception on saving entity: Document{{dataKey=A, lastValue=XXX, version=1, createdDate=Mon Apr 13 21:53:25 WIB 2020, updatedDate=Mon Apr 13 22:34:28 WIB 2020, _class=SomeData}} to collection some_data
    at org.springframework.data.mongodb.core.ReactiveMongoTemplate.lambda$doUpdate$65(ReactiveMongoTemplate.java:1819)
    at reactor.core.publisher.FluxPeek$PeekSubscriber.onNext(FluxPeek.java:177)
    at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onNext(FluxOnErrorResume.java:73)
    at reactor.core.publisher.MonoFlatMapMany$FlatMapManyInner.onNext(MonoFlatMapMany.java:242)
    at com.mongodb.reactivestreams.client.internal.SingleResultObservableToPublisher$1.onNext(SingleResultObservableToPublisher.java:42)
    at com.mongodb.reactivestreams.client.internal.ObservableToPublisher$1.onNext(ObservableToPublisher.java:66)
    at com.mongodb.async.client.AbstractSubscription.onNext(AbstractSubscription.java:142)
    at com.mongodb.async.client.AbstractSubscription.processResultsQueue(AbstractSubscription.java:217)
    at com.mongodb.async.client.AbstractSubscription.tryProcessResultsQueue(AbstractSubscription.java:172)
    at com.mongodb.async.client.SingleResultCallbackSubscription$1.onResult(SingleResultCallbackSubscription.java:48)
    at com.mongodb.async.client.MongoCollectionImpl$4.onResult(MongoCollectionImpl.java:647)
    at com.mongodb.async.client.MongoCollectionImpl$4.onResult(MongoCollectionImpl.java:641)
    at com.mongodb.async.client.MongoCollectionImpl$10.onResult(MongoCollectionImpl.java:1138)
    at com.mongodb.async.client.MongoCollectionImpl$10.onResult(MongoCollectionImpl.java:1122)
    at com.mongodb.internal.async.ErrorHandlingResultCallback.onResult(ErrorHandlingResultCallback.java:49)
    at com.mongodb.async.client.OperationExecutorImpl$2$1$1.onResult(OperationExecutorImpl.java:140)
    at com.mongodb.internal.async.ErrorHandlingResultCallback.onResult(ErrorHandlingResultCallback.java:49)
    at com.mongodb.operation.OperationHelper$ConnectionReleasingWrappedCallback.onResult(OperationHelper.java:432)
    at com.mongodb.operation.MixedBulkWriteOperation.addBatchResult(MixedBulkWriteOperation.java:527)
    at com.mongodb.operation.MixedBulkWriteOperation.access$1600(MixedBulkWriteOperation.java:72)
    at com.mongodb.operation.MixedBulkWriteOperation$6.onResult(MixedBulkWriteOperation.java:507)
    at com.mongodb.operation.MixedBulkWriteOperation$6.onResult(MixedBulkWriteOperation.java:479)
    at com.mongodb.internal.async.ErrorHandlingResultCallback.onResult(ErrorHandlingResultCallback.java:49)
    at com.mongodb.internal.connection.DefaultServer$DefaultServerProtocolExecutor$2.onResult(DefaultServer.java:245)
    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:467)
    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:399)
    at com.mongodb.internal.connection.InternalStreamConnection$2$1.onResult(InternalStreamConnection.java:376)
    at com.mongodb.internal.connection.InternalStreamConnection$MessageHeaderCallback$MessageCallback.onResult(InternalStreamConnection.java:677)
    at com.mongodb.internal.connection.InternalStreamConnection$MessageHeaderCallback$MessageCallback.onResult(InternalStreamConnection.java:644)
    at com.mongodb.internal.connection.InternalStreamConnection$5.completed(InternalStreamConnection.java:514)
    at com.mongodb.internal.connection.InternalStreamConnection$5.completed(InternalStreamConnection.java:511)
    at com.mongodb.connection.netty.NettyStream.readAsync(NettyStream.java:233)
    at com.mongodb.internal.connection.InternalStreamConnection.readAsync(InternalStreamConnection.java:511)
    at com.mongodb.internal.connection.InternalStreamConnection.access$1000(InternalStreamConnection.java:76)
    at com.mongodb.internal.connection.InternalStreamConnection$MessageHeaderCallback.onResult(InternalStreamConnection.java:634)
    at com.mongodb.internal.connection.InternalStreamConnection$MessageHeaderCallback.onResult(InternalStreamConnection.java:619)
    at com.mongodb.internal.connection.InternalStreamConnection$5.completed(InternalStreamConnection.java:514)
    at com.mongodb.internal.connection.InternalStreamConnection$5.completed(InternalStreamConnection.java:511)
    at com.mongodb.connection.netty.NettyStream.readAsync(NettyStream.java:233)
    at com.mongodb.connection.netty.NettyStream.handleReadResponse(NettyStream.java:263)
    at com.mongodb.connection.netty.NettyStream.access$800(NettyStream.java:69)
    at com.mongodb.connection.netty.NettyStream$InboundBufferHandler.channelRead0(NettyStream.java:322)
    at com.mongodb.connection.netty.NettyStream$InboundBufferHandler.channelRead0(NettyStream.java:319)
    at io.netty.channel.SimpleChannelInboundHandler.channelRead(SimpleChannelInboundHandler.java:99)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:377)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:363)
    at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:355)
    at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:377)
    at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:363)
    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)
    at java.lang.Thread.run(Thread.java:748)

I have @Version already on my Entity and when i was debuging, i saw that the version is greater than the existing value in database. in database the version is 0 and exactly when i try to save(data) the version has increased to 1 which is correct.

I have already read the spring docs about Mongodb Optimistic Locking and i think i didn't do anything wrong :')

Please take a look my entity below

@Data
@EqualsAndHashCode(callSuper = true)
@SuperBuilder
@NoArgsConstructor
@Document(collection = CollectionName.SOME_DATA)
public class SomeData implement Serializable {
  private static final long serialVersionUID = 1L;

  @Id
  @Field(value = "_id")
  private String id;

  @Version
  @Field(value = "version")
  private Long version;

  @CreatedDate
  @Field(value = "createdDate")
  private Date createdDate;

  @LastModifiedDate
  @Field(value = "updatedDate")
  private Date updatedDate;

  @Indexed
  @Field(value = "dataKey")
  private String dataKey;

  @Indexed
  @Field(value = "lastValue")
  private String lastValue;

}

Solution

  • in case someone encounter the same issue, on our legacy project we using spring data mongo version org.springframework.data:spring-data-mongodb:1.10.23.RELEASE and on this legacy code we also overriding the mongo id.

    And it seem the root cause is because we force the spring data mongo to _id instead id this make the mongo object converter to save id as String instead of mongo object. The solution is simply remove @Field(value = "_id") or dont modify id as other string alias. But if you want to make alias for this id I suppose you need extend the mongo object converter class MappingMongoConverter and make your own implementation.

    note: the unexpected behavior happen because on the new version of spring data mongo have different implementation on mongo object converter MappingMongoConverter class when handling alias for mongo id. If the alias not exactly id then when saving data id will be saved as String