How to do a Mongo aggregation query in Spring Data? How to do a Mongo aggregation query in Spring Data? spring spring

How to do a Mongo aggregation query in Spring Data?


Although this is old thread, but I hope whoever found this thread can now safely for doing multi stage/pipeline aggregation(not quite sure what it's call) in MongoRepository.As I'm also struggling looking for clue and example of aggregation in mongo repository without mongo template.

But now, I'm able to do the Aggregation pipeline as per spring doc said in here

My aggregation looks like this in mongoshell:

db.getCollection('SalesPo').aggregate([    {$project: {        month: {$month: '$poDate'},        year: {$year: '$poDate'},        amount: 1,        poDate: 1     }},      {$match: {$and : [{year:2020} , {month:7}]      }}      ,      {$group: {           '_id': {            month: {$month: '$poDate'},            year: {$year: '$poDate'}           },          totalPrice: {$sum: {$toDecimal:'$amount'}},          }      },    {$project: {        _id: 0,        totalPrice: {$toString: '$totalPrice'}     }} ])

While I transform it into @Aggregation annotation in MongoRepository become like this below (I'm removing the aposthrephe and also replace with method params):

@Repositorypublic interface SalesPoRepository extends MongoRepository<SalesPo, String> {@Aggregation(pipeline = {"{$project: {\n" +        "        month: {$month: $poDate},\n" +        "        year: {$year: $poDate},\n" +        "        amount: 1,\n" +        "        poDate: 1\n" +        "     }}"        ,"{$match: {$and : [{year:?0} , {month:?1}] \n" +        "     }}"        ,"{$group: { \n" +        "          '_id': {\n" +        "            month: {$month: $poDate},\n" +        "            year: {$year: $poDate} \n" +        "          },\n" +        "          totalPrice: {$sum: {$toDecimal:$amount}},\n" +        "          }\n" +        "      }"    ,"{$project: {\n" +        "        _id: 0,\n" +        "        totalPrice: {$toString: $totalPrice}\n" +        "     }}"})    AggregationResults<SumPrice> sumPriceThisYearMonth(Integer year, Integer month);

My Document looks like this:

@Document(collection = "SalesPo")@Datapublic class SalesPo {  @Id  private String id;  @JsonSerialize(using = LocalDateSerializer.class)  private LocalDate poDate;  private BigDecimal amount;}

And the SumPrice class for hold the projections:

@Datapublic class SumPrice {  private BigDecimal totalPrice;}

I hope this answer can help whoever try to do aggregation in mongorepository without using mongotemplate.


You can implement the AggregationOperation and write the custom aggregation operation query and then use MongoTemplate to execute any mongo shell query you have executed in your mongo shell as below:

Custom Aggregation Operation

import org.springframework.data.mongodb.core.aggregation.AggregationOperation;import org.springframework.data.mongodb.core.aggregation.AggregationOperationContext;public class CustomAggregationOperation implements AggregationOperation {  private String jsonOperation;  public CustomAggregationOperation(String jsonOperation) {    this.jsonOperation = jsonOperation;  }  @Override  public org.bson.Document toDocument(AggregationOperationContext aggregationOperationContext) {    return aggregationOperationContext.getMappedObject(org.bson.Document.parse(jsonOperation));  }}

Any Mongo Shell Aggregation query executor

import org.springframework.beans.factory.annotation.Autowired;import org.springframework.data.mongodb.core.MongoTemplate;import org.springframework.data.mongodb.core.aggregation.Aggregation;import org.springframework.data.mongodb.core.aggregation.AggregationOperation;import org.springframework.data.mongodb.core.aggregation.AggregationResults;import org.springframework.data.mongodb.core.aggregation.TypedAggregation;import org.springframework.stereotype.Service;import sample.data.mongo.models.Course;@Servicepublic class LookupAggregation {  @Autowired  MongoTemplate mongoTemplate;  public void LookupAggregationExample() {    AggregationOperation unwind = Aggregation.unwind("studentIds");    String query1 = "{$lookup: {from: 'student', let: { stuId: { $toObjectId: '$studentIds' } },"        + "pipeline: [{$match: {$expr: { $eq: [ '$_id', '$$stuId' ] },},}, "        + "{$project: {isSendTemplate: 1,openId: 1,stu_name: '$name',stu_id: '$_id',},},], "        + "as: 'student',}, }";    TypedAggregation<Course> aggregation = Aggregation.newAggregation(        Course.class,        unwind,        new CustomAggregationOperation(query1)    );    AggregationResults<Course> results =        mongoTemplate.aggregate(aggregation, Course.class);    System.out.println(results.getMappedResults());  }}

For more details, Have a look at the Github repository classes: CustomAggregationOperation & LookupAggregation

Other approaches also using MongoTemplate:

#1. Define an interface for your custom code for Model Post:

interface CustomPostRepository {     List<Post> yourCustomMethod();}

#2. Add implementation for this class and follow the naming convention to make sure we can find the class.

class CustomPostRepositoryImpl implements CustomPostRepository {    @Autowired    private MongoOperations mongoOperations;    public List<Post> yourCustomMethod() {      // custom match queries here      MatchOperation match = null;      // Group by , Lookup others stuff goes here      // For details: https://docs.spring.io/spring-data/mongodb/docs/current/api/org/springframework/data/mongodb/core/aggregation/Aggregation.html      Aggregation aggregate = Aggregation.newAggregation(match);      AggregationResults<Post> orderAggregate = mongoOperations.aggregate(aggregate,                      Post.class, Post.class);      return orderAggregate.getMappedResults();    }}

#3. Now let your base repository interface extend the custom one and the infrastructure will automatically use your custom implementation:

interface PostRepository extends CrudRepository<Post, Long>, CustomPostRepository {}