【问题标题】:How to retrieve matching element in array in spring mongodb ?如何在spring mongodb中检索数组中的匹配元素?
【发布时间】:2017-04-10 14:57:02
【问题描述】:

我正在尝试检索具有特定“_id”的文档和具有另一个特定“_id”的单个嵌入文档。

我的文档是一个目录,它包含一系列课程。

示例数据:

'_id': ObjectId('1111'),
'name': 'example catalog',
...
...
'courses': [
     { 
         '_id': ObjectId('2222'),
         'name': 'my course',
         ...
     },
     {
         ....
     }

在 mongod 中我运行这个聚合查询,然后得到我想要的:

db.getCollection('catalogs').aggregate(
{ $match: { '_id': ObjectId('58e8da206ca4f710bab6ef74') } },
{ $unwind: '$courses' },
{ $match: { 'courses._id': ObjectId('58d65541495c851c1703c57f') } })

正如我之前提到的,我已经获得了包含单个课程实例的单个目录实例。

在我的 java 存储库中,我尝试做同样的事情:

    Aggregation aggregation = Aggregation.newAggregation(
            Aggregation.match(Criteria.where(Catalog.ID_FIELD).is(catalogId)),
            Aggregation.unwind(Catalog.COURSES_FIELD, true),
            Aggregation.match(Criteria.where(Catalog.COURSES_FIELD + '.' + Course.ID_FIELD).is(embeddedCourseId))
    );
    AggregationResults<Catalog> results = mongoTemplate.aggregate(aggregation,
            Catalog.class, Catalog.class);

    List<Catalog> catalog  = results.getMappedResults();

但不幸的是,我有一个“示例目录”实例,其中包含空的课程数组。

在调试的时候,我发现在results里面,有两个props返回了。 第一个是我用过的,叫做mappedResults(代表从mongoDB返回的转换对象)——包含一个空的课程数组。 另一个是rawResults,(表示数据为DBObject) - 包含我查询的具体课程

我的 Catalog 类包含一个 ArrayList(如果有什么不同的话)。

请帮助并让我知道我应该怎么做才能正确转换结果,或者如果我在代码中做错了什么。

【问题讨论】:

    标签: java spring mongodb aggregation-framework


    【解决方案1】:

    您可以尝试以下选项。关键是在映射响应时保留结构。

    常规查询:

    使用$positional 投影

    Query query = new Query();
    query.addCriteria(Criteria.where("id").is(new ObjectId("58e8da206ca4f710bab6ef74")).and("courses.id").is(new ObjectId("58d65541495c851c1703c57f")));
    query.fields().include("name").position("courses", 1);
    List<Course> courses = mongoTemplate.find(query, Course.class);
    

    使用$elemMatch 投影

    Query query = new Query();
    query.addCriteria(Criteria.where("id").is(new ObjectId("58e8da206ca4f710bab6ef74")));
    query.fields().include("name").elemMatch("courses", Criteria.where("_id").is(new ObjectId("58d65541495c851c1703c57f") ) );
    List<Course> Course = mongoTemplate.find(query, Course.class);
    

    聚合

    Mongo 版本 >= 3.4 & Spring 1.5.2 Boot / Spring 1.10.1 Mongo。

    您可以使用$addFields 阶段,该阶段将使用$filter 值覆盖courses 字段,同时保留所有现有属性。我在当前的春季版本中找不到任何addFields builder。所以我必须使用AggregationOperation 来创建一个新的。

    AggregationOperation addFields = new AggregationOperation() {
        @Override
        public DBObject toDBObject(AggregationOperationContext aggregationOperationContext) {
            DBObject dbObject =
                    new BasicDBObject("courses",
                            new BasicDBObject("$filter",
                                    new BasicDBObject("input", "$$courses").
                                            append("as", "course").
                                            append("cond",
                                                new BasicDBObject("$eq", Arrays.<Object>asList("$$course._id", new ObjectId("58d65541495c851c1703c57f"))))));
            return new BasicDBObject("$addFields", dbObject);
        }
    };
    
    Aggregation aggregation = Aggregation.newAggregation(
                Aggregation.match(Criteria.where("_id").is(new ObjectId("58e8da206ca4f710bab6ef74"))),
                addFields
     );
    

    Mongo 版本 = 3.2 & Spring 1.5.2 Boot / Spring 1.10.1 Mongo..

    这个想法仍然与上面相同,但此管道使用$project,因此您必须添加所有要保留在最终响应中的字段。还使用了 spring 辅助方法来创建$filter 管道。

    Aggregation aggregation = newAggregation(
         Aggregation.match(Criteria.where("id").is(new ObjectId("58e8da206ca4f710bab6ef74"))),
         Aggregation.project("name")
                     .and(ArrayOperators.Filter.filter("courses").as("course")                          
                     .by(ComparisonOperators.Eq.valueOf("course._id").equalToValue(new ObjectId("58d65541495c851c1703c57f")))
                        ).as("courses")
     );
    

    Mongo 版本

    您必须使用 $unwind 并添加一个 course 字段才能让 spring 正确映射它。

    【讨论】:

    • 嗨 Veeram,非常感谢,我已经使用投影而不是展开和匹配来实现我的代码 - 它工作得很好:)
    • 不客气。添加了更多基于非聚合的选项,因为您在 _id 上匹配课程并且始终是唯一的。当数组中有多个匹配条目时,$filter 是一种通用方法。
    • 感谢您提出的所有修改建议。赞赏。我只是在这里研究不同的例子。
    • 实际上,我做了一个通用方法,可以按任何字段过滤课程。但仍然感谢您将其添加到您的答案中。
    【解决方案2】:

    您在这里遇到的问题是您的Catalog 类有一个courses 字段映射到List/ArrayList。但是,当您的聚合查询展开课程数组时,它会将courses 字段作为子文档输出。 Spring 映射器不知道如何处理它,因为它与您的 Catalog 对象结构不匹配。

    您还没有在这里完全定义您的问题,但可能更有意义的是,如果您让聚合返回 Course 对象而不是 Catalog 对象。为此,您需要向聚合管道添加一个投影阶段,以使结果看起来与单个 Course 对象完全一样。关键是从MongoDB传回来的数据需要和你的对象结构相匹配。

    【讨论】:

      猜你喜欢
      • 2013-03-03
      • 2016-06-29
      • 1970-01-01
      • 1970-01-01
      • 2020-02-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-05-27
      相关资源
      最近更新 更多