问题
这个一个springboot中使用spring-boot-starter-data-mongodb操作mongo时遇到的错误。完整的错误日志如下:
org.springframework.dao.DuplicateKeyException: E11000 duplicate key error collection: pile.Collection1 index: _id_ dup key: { _id: ObjectId('5f862a618b85fa49eff8789e') }; nested exception is com.mongodb.MongoWriteException: E11000 duplicate key error collection: pile.Collection1 index: _id_ dup key: { _id: ObjectId('5f862a618b85fa49eff8789e') }
at org.springframework.data.mongodb.core.MongoExceptionTranslator.translateExceptionIfPossible(MongoExceptionTranslator.java:99)
at org.springframework.data.mongodb.core.MongoTemplate.potentiallyConvertRuntimeException(MongoTemplate.java:2874)
at org.springframework.data.mongodb.core.MongoTemplate.execute(MongoTemplate.java:568)
at org.springframework.data.mongodb.core.MongoTemplate.insertDocument(MongoTemplate.java:1436)
at org.springframework.data.mongodb.core.MongoTemplate.doInsert(MongoTemplate.java:1236)
at org.springframework.data.mongodb.core.MongoTemplate.insert(MongoTemplate.java:1168)
at com.fp.chargepile.server.MainServerHandler.channelRead(MainServerHandler.java:204)
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.handler.timeout.IdleStateHandler.channelRead(IdleStateHandler.java:287)
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:1334)
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:926)
at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:134)
at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:624)
at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:559)
at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:476)
at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:438)
at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:858)
at io.netty.util.concurrent.DefaultThreadFactory$DefaultRunnableDecorator.run(DefaultThreadFactory.java:144)
at java.lang.Thread.run(Thread.java:748)
这个错误是在使用mongoTemplate做insert操作的时候发生的,造成的影响是插入第一条数据的时候OK,插入第二条数据的时候报这个错误,同事发生这个错误的数据插入失败。
发生背景
这个错误发生于迁移的时候,之前项目操作mongo使用的mongo-client,进行操作的,在mongo集合的实体使用的ObjectId类型声明了下主键_id,使用mongo-client进行insert的时候,实体中_id字段不赋值,插入的时候client会自动处理主键字段,实现正确的插入,使用mongo-client进行插入的代码如下:
public void insert(T t) {
MongoClient mongoClient = null;
try {
String user = NacosConfigLoad.getMongoUser();
String databaseName = NacosConfigLoad.getMongoDatabase();
char[] password = NacosConfigLoad.getMongoPassword().toCharArray();
MongoCredential credential = MongoCredential.createCredential(user, databaseName, password);
mongoClient = MongoClients.create(
MongoClientSettings.builder()
.applyToClusterSettings(builder ->
builder.hosts(Arrays.asList(new ServerAddress(NacosConfigLoad.getMongoUrl(), NacosConfigLoad.getMongoPort()))))
.credential(credential)
.build());
// create codec registry for POJOs
CodecRegistry pojoCodecRegistry = fromRegistries(MongoClientSettings.getDefaultCodecRegistry(),
fromProviders(PojoCodecProvider.builder().automatic(true).build()));
// get handle to "mydb" database
final MongoDatabase mongoDatabase = mongoClient.getDatabase(databaseName).withCodecRegistry(pojoCodecRegistry);
MongoCollection collection = mongoDatabase.getCollection(getCollectionName(), getClazz());
t.setDate(DateHandler.mongoDate());
collection.insertOne(t);
} finally {
if (null != mongoClient) {
mongoClient.close();
}
}
}
上面的功能执行起来是没有问题的,同样的功能,迁移到springboot项目中,使用下列代码进行插入操作:
mongoTemplate.insert(t);
发现出现上面的错误。
解决
通过搜索,找打一些说法,
You'r using a primitive long which has an implicit, pre-assigned value. Hence, that value is passed to MongoDB and it persists it as is as it assumes you'd like to define identifiers manually.
The trick is to just use a wrapper type, as this can be null, MongoDB detects the absence of the value and will auto-populate an ObjectID for you. However, Long is not going to work as ObjectIDs do not fit into the number space of a Long. The id types supported for auto-generation are ObjectId, String and BigInteger.
大概意思是说实体中_id字段使用的是Long类型,spring-boot-data-mongo对于long类型,不支持自增运算,支持自增运算的类型包括BigInteger,String,ObjectId。
但是我的那个字段的类型是ObjectId的,显然不是由于上面原因导致的。但是如果Long类型定义id字段的小伙伴可以参考上面的做法,更改下类型。
我的插入集合中的实体定义的算是继承关系比较复杂,我的实体定义关系如下:
public class M(){
ObjectId _id;
}
public class B() extend M{
}
public class T() extend B{
}
T t = new T();
mongoTemplate.insert(t);
最后,把超类M中的ObjectId _id 那行代码去掉,让它自己去增加定义处理,发现问题解决。
网友评论