保存对象时出错 - 保存实体时出现乐观锁异常

Error when save Object - Optimistic lock exception on saving entity

我只是在做简单的交易,比如:

  1. findByVariable(字符串变量);
  2. 处理数据,然后
  3. 保存(数据);

但我得到了这样的异常

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(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.onNext(SingleResultObservableToPublisher.java:42)
    at com.mongodb.reactivestreams.client.internal.ObservableToPublisher.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.onResult(SingleResultCallbackSubscription.java:48)
    at com.mongodb.async.client.MongoCollectionImpl.onResult(MongoCollectionImpl.java:647)
    at com.mongodb.async.client.MongoCollectionImpl.onResult(MongoCollectionImpl.java:641)
    at com.mongodb.async.client.MongoCollectionImpl.onResult(MongoCollectionImpl.java:1138)
    at com.mongodb.async.client.MongoCollectionImpl.onResult(MongoCollectionImpl.java:1122)
    at com.mongodb.internal.async.ErrorHandlingResultCallback.onResult(ErrorHandlingResultCallback.java:49)
    at com.mongodb.async.client.OperationExecutorImpl.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.access00(MixedBulkWriteOperation.java:72)
    at com.mongodb.operation.MixedBulkWriteOperation.onResult(MixedBulkWriteOperation.java:507)
    at com.mongodb.operation.MixedBulkWriteOperation.onResult(MixedBulkWriteOperation.java:479)
    at com.mongodb.internal.async.ErrorHandlingResultCallback.onResult(ErrorHandlingResultCallback.java:49)
    at com.mongodb.internal.connection.DefaultServer$DefaultServerProtocolExecutor.onResult(DefaultServer.java:245)
    at com.mongodb.internal.async.ErrorHandlingResultCallback.onResult(ErrorHandlingResultCallback.java:49)
    at com.mongodb.internal.connection.CommandProtocolImpl.onResult(CommandProtocolImpl.java:85)
    at com.mongodb.internal.connection.DefaultConnectionPool$PooledConnection.onResult(DefaultConnectionPool.java:467)
    at com.mongodb.internal.connection.UsageTrackingInternalConnection.onResult(UsageTrackingInternalConnection.java:111)
    at com.mongodb.internal.async.ErrorHandlingResultCallback.onResult(ErrorHandlingResultCallback.java:49)
    at com.mongodb.internal.connection.InternalStreamConnection.onResult(InternalStreamConnection.java:399)
    at com.mongodb.internal.connection.InternalStreamConnection.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.completed(InternalStreamConnection.java:514)
    at com.mongodb.internal.connection.InternalStreamConnection.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.access00(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.completed(InternalStreamConnection.java:514)
    at com.mongodb.internal.connection.InternalStreamConnection.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.access0(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.run(SingleThreadEventExecutor.java:989)
    at io.netty.util.internal.ThreadExecutorMap.run(ThreadExecutorMap.java:74)
    at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
    at java.lang.Thread.run(Thread.java:748)

我的实体上已经有 @Version,当我调试时,我看到版本大于数据库中的现有值。在数据库中 version0 并且当我尝试 save(data)version 已经增加到 1 这是正确的。

我已经阅读了关于 Mongodb Optimistic Locking 的 spring 文档,我认为我没有做错任何事:')

请看下面我的实体

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

}

以防有人遇到同样的问题,在我们的遗留项目中,我们使用 spring 数据 mongo 版本 org.springframework.data:spring-data-mongodb:1.10.23.RELEASE,在这个遗留代码中,我们也覆盖了 mongo id.

似乎根本原因是因为我们强制 spring 数据 mongo 为 _id 而不是 id 这使得 mongo 对象转换器为将 id 保存为字符串而不是 mongo 对象。解决方案是简单地删除 @Field(value = "_id") 或不要将 id 修改为其他字符串别名。但是如果你想为这个 id 创建别名,我想你需要扩展 mongo 对象转换器 class MappingMongoConverter 并自己实现。

注意:意外行为的发生是因为在 spring 的新版本上数据 mongo 在 mongo 对象转换器 MappingMongoConverter class 上有不同的实现处理 mongo id 的别名。如果别名不完全 id 那么在保存数据时 id 将被保存为 String