MongoDB新增Long型主键:根本原因以及对insert,save的影响

如果采用自定义注解的方式,来完成对mongoTemplate的insert方法的变更,使得能够写入Long型主键到mongodb,但是经测试,同时对save方法也造成了影响,本来save方法做更新操作十分方便,传入一个DO,直接进行一个update操作,但是重写了onBeforeConvert方法后,save方法也会从sequence这个collection里面拿自增序列,因此会新增一条记录,而不是根据传入的主键id进行update

印证了,spring-data-mongodb的生命周期中对save的影响:

onBeforeConvert:在 object 被MongoConverter转换为Document之前,在MongoTemplate insertinsertListsave操作中调用。

于是还是先看下insert直接写Long型主键的原因

private void assertUpdateableIdIfNotSet(Object entity) {

MongoPersistentEntity<?> persistentEntity = mappingContext.getPersistentEntity(entity.getClass());
MongoPersistentProperty idProperty = persistentEntity == null ? null : persistentEntity.getIdProperty();

if (idProperty == null || persistentEntity == null) {
return;
}

Object idValue = persistentEntity.getPropertyAccessor(entity).getProperty(idProperty);

if (idValue == null && !MongoSimpleTypes.AUTOGENERATED_ID_TYPES.contains(idProperty.getType())) {
throw new InvalidDataAccessApiUsageException(
String.format("Cannot autogenerate id of type %s for entity of type %s!", idProperty.getType().getName(),
entity.getClass().getName()));
}
}

这里有个判断条件,如果id的值为空,或者id的类型不在MongoSimpleTypes.AUTOGENERATED_ID_TYPES当中,都会失败

看了下后面这个类型列表,只要不是这三种类型,都会失败,所以String主键可以直接插入成功

public abstract class MongoSimpleTypes {

public static final Set<Class<?>> AUTOGENERATED_ID_TYPES;

static {
Set<Class<?>> classes = new HashSet<Class<?>>();
classes.add(ObjectId.class);
classes.add(String.class);
classes.add(BigInteger.class);
AUTOGENERATED_ID_TYPES = Collections.unmodifiableSet(classes);

但是再回味一下判断条件,两个条件是&&,这么说来,正常无法插入Long型主键的原因是既没有传ID,类型也不对,才失败,假如我们insert的时候强行传入一个Long型ID值,经过测试是能够成功的

这个就和我想的完全不一样了,不过这也给我了一个方便,假如是有数据迁移,从mysql将历史数据迁移到mongodb,那么主键就可以原封不动地get然后insert过来,包括主键

但是问题又来了,历史数据迁移,insert接口指定Long型id来进行,而新数据创建,不指定id,想插入Long型id得覆盖onBeforeConvert方法,可以覆盖了之后,历史数据指定Long型id来进行insert,最终得到的id并不是指定的这个id,而是从sequence里面获取的,因为覆盖onBeforeConvert方法修改的是全局的insert方法,貌似循环了

总结一下两者区别:

(1)新数据insert,主键不传,添加递增注解,id递增生成

(2)历史数据insert,主键要传,添加递增注解,id为传入的id

这下就明朗了,区别就是到底有没有传id,因此可以覆盖onBeforeConvert方法,添加注解递增,但是在设置id值的时候,新加一个判断条件,当这个id有@AutoIncKey注解,并且字段值没传的时候,才从自增序列sequence里获取,反之不获取,就是传入的id

更重要的是对save的影响也消除了,因为save也传了id,完美

具体修改如下,就加了一个field.get(source) == null

package com.lihuia.demo.util;

import com.lihuia.demo.annotation.AutoIncKey;
import com.lihuia.demo.entity.SeqInfo;
import org.springframework.data.mongodb.core.FindAndModifyOptions;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.mapping.event.AbstractMongoEventListener;
import org.springframework.data.mongodb.core.mapping.event.BeforeConvertEvent;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;

import javax.annotation.Resource;
import java.lang.reflect.Field;

@Component
public class InsertEventListener extends AbstractMongoEventListener<Object> {

@Resource
private MongoTemplate mongoTemplate;

@Override
public void onBeforeConvert(BeforeConvertEvent<Object> event) {
Object source = event.getSource();
if (source != null) {
ReflectionUtils.doWithFields(source.getClass(), new ReflectionUtils.FieldCallback() {
@Override
public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
ReflectionUtils.makeAccessible(field);
/** 字段满足:添加了AutoIncKey注解;字段的值为空 */
if (field.isAnnotationPresent(AutoIncKey.class) && field.get(source) == null) {
/** 设置自增ID */
field.set(source, getNextId(source.getClass().getSimpleName()));
}
}
});
}
}


private Long getNextId(String collectionName) {
Query query = new Query(Criteria.where("collectionName").is(collectionName));
Update update = new Update();
/** 主键递增1,设置为Long型 */
update.inc("seqId", 1L);
FindAndModifyOptions options = new FindAndModifyOptions();
options.upsert(true);
options.returnNew(true);
/** 原子操作,修改并返回 */
SeqInfo seqInfo = mongoTemplate.findAndModify(query, update, options, SeqInfo.class);
return seqInfo.getSeqId();
}
}

亲测无误,OVER

发表评论