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

 2019-11-02 21:09  阅读(1362)
文章分类:Spring boot

最近项目使用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;
        }
    }

来源:http://ddrv.cn

点赞(0)
版权归原创作者所有,任何形式转载请联系作者; Java 技术驿站 >> spring-data-mongo 关于_id 字段解析源码分析

相关推荐