javaspringmongodbspring-dataspring-data-mongodb

How to do a Mongo aggregation query in Spring Data?


It's the first time I am using Mongo in Java and I am having some problems with this aggregation query. I can do some simple queries in Mongo for Spring with @Query annotation in my Repository interface which extends the MongoRepository<T, ID>. It would be helpful to know which approach to take when you do long aggregations in Spring-Data.

db.post.aggregate([
    {
      $match: {}
    },
    {
      $lookup: {
        from: "users",
        localField: "postedBy",
        foreignField: "_id",
        as: "user"
      }
    },
    {
      $group: {
        _id: {
          username: "$user.name",
          title: "$title",
          description: "$description",
          upvotes: { $size: "$upvotesBy" },
          upvotesBy: "$upvotesBy",
          isUpvoted: { $in: [req.query.userId, "$upvotesBy"] },
          isPinned: {
            $cond: {
              if: { $gte: [{ $size: "$upvotesBy" }, 3] },
              then: true,
              else: false
            }
          },
          file: "$file",
          createdAt: {
            $dateToString: {
              format: "%H:%M %d-%m-%Y",
              timezone: "+01",
              date: "$createdAt"
            }
          },
          id: "$_id"
        }
      }
    },
    { $sort: { "_id.isPinned": -1, "_id.createdAt": -1 } }
])

Solution

  • 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;
    
    @Service
    public 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 {
    
    }