skip to Main Content

I am new to MongoDB. I have a collection like:

[
   {
      "_id":"1",
      "travels":[
         {
            "_id":"1"
         },
         {
            "_id":"2"
         },
         {
            "_id":"3"
         }
      ]
   },
   {
      "_id":"2",
      "travels":[
         {
            "_id":"4"
         },
         {
            "_id":"5"
         },
         {
            "_id":"6"
         }
      ]
   }
]

My Java model is more or less like this (using public modifiers for simplicity):

public class TravelsDocument<T> {
    @Id
    public String id;
    public List<Travel> travels;
}
public class Travel {
    public String id;
}

All ids in travels arrays are unique. In my Spring application I would like to select a single travel element matching specified id.

I’ve finally figured out how to do that in MongoDB.
First, I applied a filter:

{travels: {$elemMatch: { _id: 'someId'}}}

and then a projection:

{
   "_id":0,
   "travel":{
      "$arrayElemAt":[
         "$travels",
         {
            "$indexOfArray":[
               "$travels._id",
               "someId"
            ]
         }
      ]
   }
}

Then I tried to use it in an aggregation:

@Aggregation(pipeline = {
        "{'$match' :{'travels': {$elemMatch: { _id: :#{#id}}}}}",
        "{'$project' :{_id:0,     'travel':{$arrayElemAt: [ '$travels', {$indexOfArray :['$travels._id', :#{#id}]} ] }}}"})
    Mono<Travel> findSingleTravelByTenantAndId(@Param("id") String id);

The issue is that this method doesn’t return my Travel object but rather org.bson.Document object. Is there a way to return my custom object using Aggregation annotation? Is this the way to go?

2

Answers


  1. The final projection stage of the aggregation pipeline would return documents as { travel: { _id: 123 } } – note the outer travel field. But that’s not the structure of your Travel class, it only defines id (which I presume maps to _id), and probably other fields/members.

    So you need to add a $replaceWith stage:

    { $replaceWith: "$travel" }
    

    Or better, remove the project stage and use replaceWith instead:

    {
      $replaceWith: {
          "$arrayElemAt":[
             "$travels",
             {
                "$indexOfArray":[
                   "$travels._id",
                   "someId"
                ]
             }
          ]
       }
    }
    

    So the full pipeline would be:

    db.collection.aggregate([
      {
        $match: {
          travels: { $elemMatch: { _id: "someId" } }
        }
      },
      {
        $replaceWith: {
          $arrayElemAt: [
            "$travels",
            { $indexOfArray: ["$travels._id", "someId"] }
          ]
        }
      }
    ])
    

    Mongo Playground

    Login or Signup to reply.
  2. This is a variation of the query. Produces the result for the filter field travels._id value is '4':

    db.collection.aggregate([
    { 
        $match: { 
            "travels._id": '4' 
        } 
    },
    { 
        $replaceWith: { 
            $arrayElemAt: [
                { $filter: { input: "$travels", cond: { $eq: [ "$$this._id", '4' ] } } },
                0
            ]
        }
    }
    ])
    

    The output: [ { _id: '4' } ]

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search