关于Spring中MongoTemplate.aggregate的一个奇异bug

昨天在完成公司一个项目的时候用了mongoTemplate的aggregate,在使用Aggretaion.match(criteria)来筛选数据,其中criteria语句是Criteria.where("id").ne(xxxId),结果程序在执行的时候该条件一直没有起效果,但是其他的find和update等语句都是可以执行的,抱着满脑袋的疑惑翻看了它的源码实现后发现这里有一个很大的坑,具体是这个样子的:

在其他语句中,criteria查询条件最终都会包到Query对象里面,而template驱动在对Query进行解析的时候,以updateFirst为例,它的解析过程是这个样子的:

public WriteResult updateFirst(Query query, Update update, Class<?> entityClass, String collectionName) {
   return doUpdate(collectionName, query, update, entityClass, false, false);
}

首先经过updateFirst方法,然后调用doUpdate:

protected WriteResult doUpdate(final String collectionName, final Query query, final Update update,
      final Class<?> entityClass, final boolean upsert, final boolean multi) {

   return execute(collectionName, new CollectionCallback<WriteResult>() {
      public WriteResult doInCollection(DBCollection collection) throws MongoException, DataAccessException {

         MongoPersistentEntity<?> entity = entityClass == null ? null : getPersistentEntity(entityClass);

         increaseVersionForUpdateIfNecessary(entity, update);

         DBObject queryObj = query == null ? new BasicDBObject() : queryMapper.getMappedObject(query.getQueryObject(),
               entity);
         DBObject updateObj = update == null ? new BasicDBObject() : updateMapper.getMappedObject(
               update.getUpdateObject(), entity);

         if (LOGGER.isDebugEnabled()) {
            LOGGER.debug("Calling update using query: " + queryObj + " and update: " + updateObj + " in collection: "
                  + collectionName);
         }

         MongoAction mongoAction = new MongoAction(writeConcern, MongoActionOperation.UPDATE, collectionName,
               entityClass, updateObj, queryObj);
         WriteConcern writeConcernToUse = prepareWriteConcern(mongoAction);
         WriteResult writeResult = writeConcernToUse == null ? collection.update(queryObj, updateObj, upsert, multi)
               : collection.update(queryObj, updateObj, upsert, multi, writeConcernToUse);

         if (entity != null && entity.hasVersionProperty() && !multi) {
            if (writeResult.getN() == 0 && dbObjectContainsVersionProperty(queryObj, entity)) {
               throw new OptimisticLockingFailureException("Optimistic lock exception on saving entity: "
                     + updateObj.toMap().toString() + " to collection " + collectionName);
            }
         }

         handleAnyWriteResultErrors(writeResult, queryObj, MongoActionOperation.UPDATE);
         return writeResult;
      }
   });
}

在这一步中,它对query的解析是这句:

DBObject queryObj = query == null ? new BasicDBObject() : queryMapper.getMappedObject(query.getQueryObject(),

我们跟入getMappedObject方法中,它的实现是:

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 = createPropertyField(entity, key, mappingContext);
      Entry<String, Object> entry = getMappedObjectForField(field, query.get(key));

      result.put(entry.getKey(), entry.getValue());
   }

   return result;
}

在一个个地翻看每一条语句的源码之后,发现其对key进行解析的时候,getMappedObjectForfield方法负责对每一个约束key和查询值进行一一对应转换,具体实现是:

protected Entry<String, Object> getMappedObjectForField(Field field, Object rawValue) {

   String key = field.getMappedKey();
   Object value;

   if (isNestedKeyword(rawValue) && !field.isIdField()) {
      Keyword keyword = new Keyword((DBObject) rawValue);
      value = getMappedKeyword(field, keyword);
   } else {
      value = getMappedValue(field, rawValue);
   }

   return createMapEntry(key, value);
}

到了这儿的时候,其中一个条件引起了我的注意,就是field.isIdField,难道是这里对id进行了转换?

进一步跟进之后发现:

public boolean isIdField() {
   return ID_KEY.equals(name);
}
private static final String ID_KEY = "_id";

原来其在此处对_id进行了特殊处理,既然有做特殊处理,那就得看看是怎么特殊处理的了,于是进一步跟进getMappedKeyword方法,发现其中没有进一步特殊处理,于是返回上一个方法,从第一个方法跟进,打开getMappedKey:

public String getMappedKey() {
   return isIdField() ? ID_KEY : name;
}

原来在这里进行了特殊处理,随后进行了进一步查看之后发现template在Aggregation.match中没有对此进行转换,结果导致mongo查询条件一直没有正常工作,没办法,只能自行处理了,于是查询条件改为Criteria.where("_id").ne(new ObjectId(xxxId)),至此搞定。这个坑埋得有点深啊。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: mongotemplate.aggregateSpring Data MongoDB提供的一种聚合操作方法,用于对MongoDB数据数据进行聚合操作,可以实现类似于SQL的GROUP BY、SUM、COUNT等操作。通过mongotemplate.aggregate方法,可以使用MongoDB的聚合管道对数据进行处理,包括筛选、分组、排序、计算等操作,从而得到需要的结果。 ### 回答2: MongoTemplate.aggregateSpring Data MongoDB提供的用于执行聚合操作的方法。在MongoDB,聚合操作用于在集合进行数据处理和分析,以获得需要的结果。 使用MongoTemplate.aggregate,我们可以使用聚合管道来指定一系列的聚合阶段,以处理从集合检索到的文档。聚合管道是由一系列阶段组成的,每个阶段都会对输入文档进行处理,并将处理结果传递给下一个阶段,最终生成最终的聚合结果。 聚合阶段可以包括多种操作,如过滤、分组、投影、排序、限制等。我们可以根据具体的需求选择不同的操作来构建聚合管道。 使用MongoTemplate.aggregate方法,我们可以构建一个Aggregation对象,通过调用不同的方法来添加不同的聚合阶段。例如,可以使用match方法来添加过滤阶段,使用group方法来添加分组阶段,使用project方法来添加投影阶段等。 最后,调用MongoTemplate.aggregate的结果会返回一个聚合操作的结果。可以使用聚合操作的结果来进行相关的数据分析和处理。 总之,MongoTemplate.aggregateSpring Data MongoDB提供的一个用于执行聚合操作的方法,通过构建聚合管道,可以对MongoDB集合数据进行处理和分析,并获得需要的结果。 ### 回答3: MongoTemplate.aggregateSpring Data MongoDB 一个函数,用于执行 MongoDB 的聚合操作。 聚合操作是 MongoDB 一种高级数据处理方法,用于在集合处理数据并返回结果。MongoTemplate.aggregate 函数可以通过传入不同的聚合管道操作,实现对 MongoDB 数据进行聚合操作。 聚合管道是一系列的聚合操作,每个操作都会对输入进行处理,并生成输出结果。管道操作可以包括筛选、排序、分组、计算、转换等多个步骤,可以根据具体需求来组合这些操作。 MongoTemplate.aggregate 函数的参数包括聚合管道操作和输出结果的类型。聚合管道操作可以使用 Aggregation 类提供的各种操作符,如 match、sort、group、project、limit 等。结果类型可以使用 Class 类型来指定,也可以使用 AggregationResults 类型来获取带有聚合操作结果的包装器。 使用 MongoTemplate.aggregate 函数可以轻松地执行各种聚合操作,同时结合 Spring Data MongoDB 提供的其他功能,还可以更方便地进行数据的查询、更新和删除等操作。 总的来说,MongoTemplate.aggregate 函数是 Spring Data MongoDB 提供的一个方便的接口,用于执行 MongoDB 的聚合操作,可以通过传入不同的聚合管道操作来实现各种高级数据处理需求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值