3

I am trying to run aggregation pipeline in a spring boot project using MongoTemplate and aggregation framework.

My query executes without any exception. But when I try to call the getMappedResults() on the AggregationResults instance, it always gives me an empty list. However, if I inspect the result in the debugger, I can see that the getRawResults() method returns the values.

I am using spring-boot version 1.5.9.RELEASE and spring-boot-starter-data-mongodb version 2.1.2.RELEASE

I am not sure what I am doing incorrectly.


The following is the code for aggregation

    GroupOperation groupOperation = Aggregation.group("field1", "field2")
            .count().as("count")
            .max("timestamp").as("timestamp");

    ProjectionOperation projectionOperation = Aggregation.project("field1", "field2", "count", "timestamp");

    DBObject cursor = new BasicDBObject(10);
    AggregationOptions aggregationOptions = Aggregation.newAggregationOptions().cursor(cursor).build();

    Aggregation aggregation = Aggregation.newAggregation(groupOperation, projectionOperation).withOptions(aggregationOptions);
    AggregationResults<Res> activities = mongoTemplate.aggregate(aggregation, "test_collection", Res.class);

The following is the class in which I am trying to map the result

public class Res {
    public String field1;

    public String field2;

    public Long timestamp;

    public Integer count;

    public Res() {
    }

    public Res(String field1, String field2, Long timestamp, Integer count) {
        this.field1 = field1;
        this.field2 = field2;
        this.timestamp = timestamp;
        this.count = count;
    }
}

Note If I skip the cursor in the AggregationOptions, I get the following error

'The 'cursor' option is required, except for aggregate with the explain argument'
greenPadawan
  • 1,511
  • 3
  • 17
  • 30

2 Answers2

2

I was also struggling with this problem of aggregation not mapping composite ID's and it turned out I misunderstood how mapping worked. (I'd recommend reading the Spring documentation on mapping: https://docs.spring.io/spring-data/mongodb/docs/current/reference/html/#mapping-chapter)

It's also worth noting that the mapping behaviour has changed somewhere between Spring Boot 2.1.8 and 2.3.4 (I only ran into this problem when upgrading). In 2.1.8 you could, rightly or wrongly, map composite keys to distinct fields in a single java class (such as the original Res class posted in the question).

charlycou's answer provides the clue in the code snippet of the document returned (notice that _id is a json object)

    {
     _id: {
       field1:"field1",
       field2:"field2"
     },
     count: countResult,
     timestamp:timestamp
    }

In order to get mapping to work correctly in this composite key scenario, create a java class to represent the composite key. For example:

    public class ResId {
        public String field1;
        public String field2; 
     
        public ResId(String field1, String field2){
           this.field1 = field1;
           this.field2 = field2;
       }
    }
    
    public class Res {
        @Id
        public ResId resId;
    
        public Long timestamp;
    
        public Integer count;
    
        public Res() {
        }
    
        public Res(ResId resId, Long timestamp, Integer count) {
            this.resId = resId;
            this.timestamp = timestamp;
            this.count = count;
        }
    }
Austen
  • 131
  • 8
0

From my experience , the GroupOperation would result an array of documents with the following scheme:

{
  _id: {
    field1:"field1",
    field2:"field2"
  },
  count: countResult,
  timestamp:timestamp
}

In this case the ProjectOperation would not result documents that map your entity class.

I'd suggest you to activate debug logging by adding logging.level.org.springframework.data=debug in your application.properties file. This way you could see what requests are sent to MongoDB and reproduce them in the MongoShell in order to see result of each of your aggregation operations.

A quick way to test the hypothesis that you entity class is not correctly mapped because of the GroupOperation result is to add Setters and to remove the second Constructor. count and timestamp attributes should be correctly mapped and Res object would be created with count and timestamp properties only.

Note: From this post, aggregation needs cursor since Mongo 3.6 and MongoDB 3.6 requires spring 1.5.10.RELEASE for aggregation operations. Concider upgrading Spring-boot to 1.5.10 to skip the cursor.

charlycou
  • 1,778
  • 13
  • 37
  • I tried your suggestion and added the setters and removed all the constructors but the default constructor. However, it still didn't map the result. But when I upgraded to spring 1.5.10 it started working. I'm not sure if this is an issue specific to the spring version. – greenPadawan Feb 07 '19 at 04:34
  • Did you modify your `ProjectOperation` as I suggested? Not sure this related to Spring version either. Still, the best thing to build Spring `AggregationOperation` is to copy the MongoDB query from debug logging and to test each of the operation in MongoShell. – charlycou Feb 07 '19 at 09:09
  • Yes, I modified the project operation as you suggested, but still didn't work. Anyway, after upgrading the spring version, it seems to be working – greenPadawan Feb 08 '19 at 04:29