javamongodbaggregation-frameworkspring-data-mongodbmongotemplate

Spring Boot + MongoTemplate, unable to add new field to Projection


I'm having an issue with an old aggregation query.

I have a mongo collection which has many documents containing information about credits / gifts given / awarded to our users.

Currently I am able to run an Aggregated Query to get all the users who have received a credit / gift within the last x number of days and sum the total value of those credits for each user. However, my problem is that now I want to project more fields for my mapping class which I am unable to do.

Here is the document in Mongo

 _id: ObjectId("61c36a8a21047124c4181271"),
    transactionId: UUID("6fbf536e-7a53-442c-9615-53e32362608b"),
    userId: 'xxxxx',
    transactionMessage: 'Account credited with: 1',
    transactionType: 'CREDIT',
    transactionAction: 'GIFT',
    inComingPaymentFromUserId: 'xxxx',
    awardForContentId : "abcd123242" 
    transactionAmount: Decimal128("1"),
    customMessage: "blah'",
    createdDate: ISODate("2021-12-22T18:12:26.812Z"),
    lastUpdatedAt: ISODate("2021-12-22T18:12:26.812Z"),

I can run a aggregation which gives me the correct mapping like so:

Aggregation agg = Aggregation.newAggregation(match(Criteria.where("createdDate").gt(LocalDate.now().minusDays(range))
                .andOperator(Criteria.where("transactionAction").is("GIFT"))), sort(Sort.Direction.DESC, "createdDate"),
        group("userId").sum("transactionAmount").as("totalValueAwarded"),
        skip((long) pageable.getPageNumber() * pageable.getPageSize()),
        limit(pageable.getPageSize()));
mongoTemplate.aggregate(agg, Transaction.class, RecentlyRewardedUsers.class).getMappedResults();

For Context my mapped RecentlyRewarded.class looks like this:

@Getter
@Setter
public class RecentlyRewardedUsers {

    @Field("userId")
    private String userId;
    private String totalValueAwarded;
}

And correctly the data is mapped to the two fields when the above aggregation runs.

Now, I find I need to add more fields to my RecentlyRewarded class:


@Getter
@Setter
@JsonInclude(JsonInclude.Include.NON_NULL)
public class RecentlyRewardedUsers {

    @Field("id")
    @Id
    private String userId;
    private BigDecimal totalValueAwarded;
    private String awardForContentId; //THIS IS THE NEW FIELD I've ADDED
}

I thought I would be able to just add the new field "awardforContentId" to my group query and it would be correctly mapped but that is not happening:

Aggregation agg = Aggregation.newAggregation(match(Criteria.where("createdDate").gt(LocalDate.now().minusDays(range))
                .andOperator(Criteria.where("transactionAction").is("GIFT"))), sort(Sort.Direction.DESC, "createdDate"),
        group("userId", "awardForContentId").sum("transactionAmount").as("totalValueAwarded"),
        skip((long) pageable.getPageNumber() * pageable.getPageSize()),
        limit(pageable.getPageSize()));
mongoTemplate.aggregate(agg, Transaction.class, RecentlyRewardedUsers.class).getMappedResults();

What happens is that my userId field in my POJO is now set to:

{"userId": "auth0|61b9e2f7d1fc9f0071d508f1", "awardForContentId": "637b98a85dde3949fbb4314f"}

and the awardForContentId in my class is null.

I have also tried just adding the awardForConentId to the project operation like so:

 Aggregation agg = Aggregation.newAggregation(match(Criteria.where("createdDate").gt(LocalDate.now().minusDays(range))
                        .andOperator(Criteria.where("transactionAction").is("GIFT"))), sort(Sort.Direction.DESC, "createdDate"),
                group("userId").sum("transactionAmount").as("totalValueAwarded"),
                project("userId", "totalValueAwarded", "awardForContentId"),
                skip((long) pageable.getPageNumber() * pageable.getPageSize()),
                limit(pageable.getPageSize()));

However that results in the following error:

Method threw 'java.lang.IllegalArgumentException' exception.
Invalid reference 'awardForContentId'!

What am I doing stupid here?

Many thanks


Solution

  • When you group by multiple fields, the result will look something like this.

    {
      "_id": {
        "userId": ,
        "awardForContentId": ,
      },
      "transactionAmount": 
    }
    

    So, you have to project it correctly to match you Java class.

    Aggregation agg = Aggregation.newAggregation(
        match(
            Criteria.where("createdDate").gt(LocalDate.now().minusDays(range))
            .andOperator(Criteria.where("transactionAction").is("GIFT"))
        ),
        sort(Sort.Direction.DESC, "createdDate"),
        group("userId", "awardForContentId").sum("transactionAmount").as("totalValueAwarded"),
        project()
            .and("_id.userId").as("userId")
            .and("_id.awardForContentId").as("awardForContentId")
            .andInclude("totalValueAwarded")
        skip((long) pageable.getPageNumber() * pageable.getPageSize()),
        limit(pageable.getPageSize())
    );
    

    You can remove @Field("id") @Id from the returning class.