Search code examples
springmongodbspring-dataaggregation-frameworkspring-data-mongodb

Spring Data MongoDB Lookup with Pipeline Aggregation


How would I convert the following MongoDB query into a query to be used by my Java Spring application? I can't find a way to use pipeline with the provided lookup method.

Here is the query I am attempting to convert. I also want to note that I didn't use $unwind as I wanted the deliveryZipCodeTimings to stay as a grouped collection in the return object.

db.getCollection('fulfillmentChannel').aggregate([
    {
        $match: {
            "dayOfWeek": "SOME_VARIABLE_STRING_1"
        }
    },
    {
        $lookup: {
            from: "deliveryZipCodeTiming",
            let: { location_id: "$fulfillmentLocationId" },
            pipeline: [{
                $match: {
                    $expr: {
                        $and: [
                            {$eq: ["$fulfillmentLocationId", "$$location_id"]},
                            {$eq: ["$zipCode", "SOME_VARIABLE_STRING_2"]}
                        ]
                    }
                }
            },
            { 
                $project: { _id: 0, zipCode: 1, cutoffTime: 1 } 
            }],
            as: "deliveryZipCodeTimings"
        }
    },
    {
        $match: {
            "deliveryZipCodeTimings": {$ne: []}
        }
    }
])

Solution

  • Building upon the info given by @dnickless, I was able to solve this. I'll post the complete solution in the hopes it helps someone else in the future.

    I'm using mongodb-driver:3.6.4

    First, I had to create a custom aggregation operation class so that I could pass in a custom JSON mongodb query to be used in the aggregation operation. This will allow me to use pipeline within a $lookup which is not supported with the driver version I am using.

    public class CustomProjectAggregationOperation implements AggregationOperation {
        private String jsonOperation;
    
        public CustomProjectAggregationOperation(String jsonOperation) {
            this.jsonOperation = jsonOperation;
        }
    
        @Override
        public Document toDocument(AggregationOperationContext aggregationOperationContext) {
            return aggregationOperationContext.getMappedObject(Document.parse(jsonOperation));
        }
    }
    

    Now that we have the ability to pass a custom JSON query into our mongodb spring implementation, all that is left is to plug those values into a TypedAggregation query.

    public List<FulfillmentChannel> getFulfillmentChannels(
        String SOME_VARIABLE_STRING_1, 
        String SOME_VARIABLE_STRING_2) {
    
        AggregationOperation match = Aggregation.match(
                Criteria.where("dayOfWeek").is(SOME_VARIABLE_STRING_1));
        AggregationOperation match2 = Aggregation.match(
                Criteria.where("deliveryZipCodeTimings").ne(Collections.EMPTY_LIST));
        String query =
                "{ $lookup: { " +
                        "from: 'deliveryZipCodeTiming'," +
                        "let: { location_id: '$fulfillmentLocationId' }," +
                        "pipeline: [{" +
                        "$match: {$expr: {$and: [" +
                        "{ $eq: ['$fulfillmentLocationId', '$$location_id']}," +
                        "{ $eq: ['$zipCode', '" + SOME_VARIABLE_STRING_2 + "']}]}}}," +
                        "{ $project: { _id: 0, zipCode: 1, cutoffTime: 1 } }]," +
                        "as: 'deliveryZipCodeTimings'}}";
    
        TypedAggregation<FulfillmentChannel> aggregation = Aggregation.newAggregation(
                FulfillmentChannel.class,
                match,
                new CustomProjectAggregationOperation(query),
                match2
        );
    
        AggregationResults<FulfillmentChannel> results = 
            mongoTemplate.aggregate(aggregation, FulfillmentChannel.class);
        return results.getMappedResults();
    }