Search code examples
mongodbspring-bootspring-data-mongodbspring-mongo

Update one of the field in documents while fetching results using criteria query from Spring Data Mongo


Using criteria query I am fetching documents from MongoDB. What is my requirement here is I want update a field from parent document value by querying sub document using Criteria from Spring Data Mongo. The parent document is comments and nested documents is replies. I am able to get list of comments along with sub documents using the below code,

Data Set:
{
"_id" : ObjectId("5cb726937a7148376094d393"),
"_class" : "vzi.cpei.Comments",
"text" : "first comment on money control",
"replies" : [        
    {
        "_id" : "3cfef1cd-e0da-4883-86a4-17b223639087",
        "text" : "extract the traces",
        "status" : true
    },
    {
        "_id" : "3cfef1cd-e0da-4883-86a4-17b153690087",
        "text" : "replied deiberate",
        "status" : false
    },
    {
        "_id" : "3cfef1cd-e0da-4883-86a4-17b153139087",
        "text" : "Bgm",
        "status" : true
    }],
}
Response DTO:
      
      public class CommentsDTO{
      private String id;
      private String text;
      private List<Replies> replies;
      private Integer totalReplies;
    }

Code wrote in Spring using Spring data mongo Criteria query,

    Query query = new Query();
    Criteria criteria =Criteria.where("_id").is(new ObjectId("5efe3d1f8a2ef008249f72d9"));
    query.addCriteria(criteria);
    List<Comments> comments = mongoOps.find(query,"Comments", 
  CommentsDTO.class);
    return comments;

In the result I want to update the field repliesCount with total number of replies with status true so the expected output should be,

{
"_id" : ObjectId("5cb726937a7148376094d393"),
"_class" : "vzi.cpei.Comments",
"text" : "first comment on money control",
"totalReplies" : 2
"replies" : [        
    {
        "_id" : "3cfef1cd-e0da-4883-86a4-17b223639087",
        "text" : "extract the traces",
        "status" : true
    },
    {
        "_id" : "3cfef1cd-e0da-4883-86a4-17b153690087",
        "text" : "replied deiberate",
        "status" : false
    },
    {
        "_id" : "3cfef1cd-e0da-4883-86a4-17b153139087",
        "text" : "Bgm",
        "status" : true
    }],
}

I am totally confused where to do this operation while fetching.


Solution

  • You need to perform the MongoDB aggregation.

    Explanation of pipeline

    1. We apply the $match stage to filter results (similar to .find(query))
    2. We apply the $project to transform the document structure and include totalReplies field calculated based on replies values

    Shell

    db.Comments.aggregate([
      {
        $match: {
          "_id": ObjectId("5cb726937a7148376094d393")
        }
      },
      {
        $project: {
          _class: 1,
          replies: 1,
          totalReplies: {
            $size: {
              "$filter": {
                input: "$replies.status",
                as: "status",
                cond: "$$status"
              }
            }
          }
        }
      }
    ])
    

    MongoPlayground

    Spring-Mongo

    import org.springframework.data.mongodb.core.aggregation.Aggregation;
    import org.springframework.data.mongodb.core.aggregation.AggregationResults;
    import org.springframework.data.mongodb.core.aggregation.ArrayOperators;
    import static org.springframework.data.mongodb.core.aggregation.Aggregation.*;
    ...
    
    Aggregation agg = Aggregation.newAggregation(
            match(Criteria.where("_id").is(new ObjectId("5cb726937a7148376094d393"))),
            project("_id", "_class", "replies").and(ArrayOperators.Size.lengthOfArray(
                    ArrayOperators.Filter.filter("replies.status").as("filter").by("$$filter")
                    )).as("totalReplies"));
    
    //System.out.println(agg);
    return mongoOps.aggregate(agg, mongoOps.getCollectionName(CommentsDTO.class), CommentsDTO.class);
    

    EDIT: Legacy Spring-boot 1.4.2

    project("_id", "_class", "replies")
            .and(AggregationFunctionExpressions.SIZE
                .of(new BasicDBObject("$filter",
                    new BasicDBObject("input", "$replies.status")
                        .append("as", "status")
                        .append("cond", "$$status"))))
            .as("totalReplies")