spring-data-mongo 关于_id 字段解析源码分析

最近项目使用mongo作为持久层

遇到问题:

项目中使用的主键(包括内嵌文档)都是ObjectId类型.以前实体使用String类型.

在测试的时候使用spring-data-mongo 发现数据 都能通过主键查询得到结果.

可以在上线后 发现,mongo内嵌文档通过spring-data-mongo主键查询不出来的.

数据结构类似:

{
	"_id" : ObjectId("571867bde4b0855e73cf72a8"),
	"isDel" : "0",
	"delTime" : NumberLong(0),
	"lModTime" : NumberLong(0),
	"uid" : "8af0b0e4bc671857fe7aaa59i",
	"isEnCV" : "0",
	"cvName" : "xxxx",
	"verifyTime" : NumberLong(0),
	"views" : NumberLong(0),
	"downloads" : NumberLong(0),
	"cnName" : "xxxx",
	"gender" : "xxx",
	"birthday" : NumberLong(606585600),
	"degreeId" : "5",
	"degreeName" : "xxxx",
	"marry" : "M",
	"nationality" : "中国",
	"email" : "xxxx",
        "education" : [
		{
			"_id" : ObjectId("571867b7e4b0855e73cf729f"),
			"degreeId" : "5",
			"degreeName" : "本科",
			"college" : "xxx",
			"major" : "xxxx",
			"start" : NumberLong(1188576000),
			"end" : NumberLong(1309449600),
			"description" : ""
		}
	]
}

查询语句:

主键查询: (这样是能查询到的)

(cvid = 571867bde4b0855e73cf72a8)

Query.query(Criteria.where("_id").is(Cvid))

内嵌文档查询: (这样式是查询不到的)

(eduid = 571867b7e4b0855e73cf729f)

Query.query(Criteria.where(
"education._id"
).is(eduid)
)  

于是 好奇心驱使下开始排查问题。发现spring-data-mongo对于主键查询做了优化判断.如果字段名称  

contains 匹配 _id 如果匹配上了 底层的会对入参进行默认的ObjectId转换.

而内嵌文档的查询主键是 xx._id ,所以判断是不包含的,所以没有进行ObjectId转换.

最终解决方案:

在主键查询时自己去显示调用ObjectId的判断.使查询语句能识别ObjectId类型或者String类型

Query.query(Criteria.where("education._id").is(getObjectValue(eduid)))

import org.bson.types.ObjectId;
 
 public Object getObjectValue(String value)
  {
    return ObjectId.isValid(value) ? new ObjectId(value) : value;
  }

由此我们跟踪下 spring-data-mongo怎么处理的?

spring-data-mongodb-1.3.2

package org.springframework.data.mongodb.core.convert.QueryMapper;
  
public class QueryMapper {
     
        private static final List<String> DEFAULT_ID_NAMES = Arrays.asList("id", "_id");
  
        public DBObject getMappedObject(DBObject query, MongoPersistentEntity<?> entity) {
        if (isNestedKeyword(query)) {
            return getMappedKeyword(new Keyword(query), entity);
        }
        DBObject result = new BasicDBObject();
        for (String key : query.keySet()) {
            // TODO: remove one once QueryMapper can work with Query instances directly
            if (Query.isRestrictedTypeKey(key)) {
                @SuppressWarnings("unchecked")
                Set<Class<?>> restrictedTypes = (Set<Class<?>>) query.get(key);
                this.converter.getTypeMapper().writeTypeRestrictions(result, restrictedTypes);
                continue;
            }
            if (isKeyword(key)) {
                result.putAll(getMappedKeyword(new Keyword(query, key), entity));
                continue;
            }
            Field field = entity == null ? new Field(key) : new MetadataBackedField(key, entity, mappingContext);
            Object rawValue = query.get(key);
            String newKey = field.getMappedKey();
            if (isNestedKeyword(rawValue) && !field.isIdField()) {
                Keyword keyword = new Keyword((DBObject) rawValue);
                result.put(newKey, getMappedKeyword(field, keyword));
            } else {
                result.put(newKey, getMappedValue(field, rawValue));
            }
        }
        return result;
    }
  
        private Object getMappedValue(Field documentField, Object value) {
        if (documentField.isIdField()) {
            if (value instanceof DBObject) {
                DBObject valueDbo = (DBObject) value;
                if (valueDbo.containsField("$in") || valueDbo.containsField("$nin")) {
                    String inKey = valueDbo.containsField("$in") ? "$in" : "$nin";
                    List<Object> ids = new ArrayList<Object>();
                    for (Object id : (Iterable<?>) valueDbo.get(inKey)) {
                        ids.add(convertId(id));
                    }
                    valueDbo.put(inKey, ids.toArray(new Object[ids.size()]));
                } else if (valueDbo.containsField("$ne")) {
                    valueDbo.put("$ne", convertId(valueDbo.get("$ne")));
                } else {
                    return getMappedObject((DBObject) value, null);
                }
                return valueDbo;
            } else {
                return convertId(value);
            }
        }
        if (isNestedKeyword(value)) {
            return getMappedKeyword(new Keyword((DBObject) value), null);
        }
        if (documentField.isAssociation()) {
            return convertAssociation(value, documentField.getProperty());
        }
        return convertSimpleOrDBObject(value, documentField.getPropertyEntity());
    }
  
    public Object convertId(Object id) {
        try {
            return conversionService.convert(id, ObjectId.class);
        } catch (ConversionException e) {
            // Ignore
        }
        return delegateConvertToMongoType(id, null);
   }
  
  
        private static class MetadataBackedField extends Field {
        private final MongoPersistentEntity<?> entity;
        private final MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> mappingContext;
        private final MongoPersistentProperty property;
        /**
         * Creates a new {@link MetadataBackedField} with the given name, {@link MongoPersistentEntity} and
         * {@link MappingContext}.
         *
         * @param name must not be {@literal null} or empty.
         * @param entity must not be {@literal null}.
         * @param context must not be {@literal null}.
         */
        public MetadataBackedField(String name, MongoPersistentEntity<?> entity,
                MappingContext<? extends MongoPersistentEntity<?>, MongoPersistentProperty> context) {
            //to do.......
        }
        /*
         * (non-Javadoc)
         * @see org.springframework.data.mongodb.core.convert.QueryMapper.Field#with(java.lang.String)
         */
        @Override
        public MetadataBackedField with(String name) {
            //to do.......
        }
        /*
         * (non-Javadoc)
         * @see org.springframework.data.mongodb.core.convert.QueryMapper.Field#isIdKey()
         */
        @Override
        public boolean isIdField() {
            MongoPersistentProperty idProperty = entity.getIdProperty();
            if (idProperty != null) {
                return idProperty.getName().equals(name) || idProperty.getFieldName().equals(name);
            }
            return DEFAULT_ID_NAMES.contains(name);
        }
        /*
         * (non-Javadoc)
         * @see org.springframework.data.mongodb.core.convert.QueryMapper.Field#getProperty()
         */
        @Override
        public MongoPersistentProperty getProperty() {
            return property;
        }
        /*
         * (non-Javadoc)
         * @see org.springframework.data.mongodb.core.convert.QueryMapper.Field#getEntity()
         */
        @Override
        public MongoPersistentEntity<?> getPropertyEntity() {
            //to do.......
        }
        /*
         * (non-Javadoc)
         * @see org.springframework.data.mongodb.core.convert.QueryMapper.Field#isAssociation()
         */
        @Override
        public boolean isAssociation() {
            //to do.......;
        }
        /*
         * (non-Javadoc)
         * @see org.springframework.data.mongodb.core.convert.QueryMapper.Field#getTargetKey()
         */
        @Override
        public String getMappedKey() {
            //to do.......
        }
        private PersistentPropertyPath<MongoPersistentProperty> getPath(String name) {
            //to do.......
        }
    }
}

—————————–ObjectId 校验规则 ———————————-

package org.bson.types;
       
     public class ObjectId implements Comparable<ObjectId> , java.io.Serializable {
     public static boolean isValid( String s ){
        if ( s == null )
            return false;
        final int len = s.length();
        if ( len != 24 )
            return false;
        for ( int i=0; i<len; i++ ){
            char c = s.charAt( i );
            if ( c >= '0' && c <= '9' )
                continue;
            if ( c >= 'a' && c <= 'f' )
                continue;
            if ( c >= 'A' && c <= 'F' )
                continue;
            return false;
        }
        return true;
    }
}

    原文作者:Spring Boot
    原文地址: https://blog.csdn.net/java_magicsun/article/details/67636654
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞