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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章