Spring Data MongoDB Lookup with Pipeline Aggregation

前端 未结 3 470
礼貌的吻别
礼貌的吻别 2020-12-09 12:20

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 loo

3条回答
  •  星月不相逢
    2020-12-09 13:05

    I would like to add this my solution which is repeating in some aspect the solutions posted before.

    Mongo driver v3.x

    For Mongo driver v3.x I came to the following solution:

    import java.util.Collections;
    import java.util.List;
    import java.util.Map;
    import java.util.stream.Collectors;
    
    import com.mongodb.BasicDBList;
    import com.mongodb.BasicDBObject;
    import com.mongodb.util.JSON;
    
    import org.bson.Document;
    import org.springframework.data.mongodb.core.aggregation.AggregationOperation;
    import org.springframework.data.mongodb.core.aggregation.AggregationOperationContext;
    
    public class JsonOperation implements AggregationOperation {
    
        private List documents;
    
        public JsonOperation(String json) {
            Object root = JSON.parse(json);
    
            documents = root instanceof BasicDBObject
                        ? Collections.singletonList(new Document(((BasicDBObject) root).toMap()))
                        : ((BasicDBList) root).stream().map(item -> new Document((Map) ((BasicDBObject) item).toMap())).collect(Collectors.toList());
        }
    
        @Override
        public Document toDocument(AggregationOperationContext context) {
            // Not necessary to return anything as we override toPipelineStages():
            return null;
        }
    
        @Override
        public List toPipelineStages(AggregationOperationContext context) {
            return documents;
        }
    }
    

    and then provided that aggregation steps are given in some resource aggregations.json:

    [
      {
        $match: {
          "userId": "..."
        }
      },
      {
        $lookup: {
          let: {
            ...
          },
          from: "another_collection",
          pipeline: [
            ...
          ],
          as: "things"
        }
      },
      {
        $sort: {
          "date": 1
        }
      }
    ]
    

    one can use above class as follows:

    import static org.springframework.data.mongodb.core.aggregation.Aggregation.newAggregation;
    
    Collection results = mongoTemplate.aggregate(newAggregation(new JsonOperation(resourceToString("aggregations.json", StandardCharsets.UTF_8))), "some_collection", ResultDao.class).getMappedResults();
    

    Mongo driver v4.x

    As JSON class was removed from Mongo v4, I have rewritten the class as follows:

    import java.util.Collections;
    import java.util.List;
    
    import org.bson.Document;
    import org.springframework.data.mongodb.core.aggregation.AggregationOperation;
    import org.springframework.data.mongodb.core.aggregation.AggregationOperationContext;
    
    public class JsonOperation implements AggregationOperation {
    
        private List documents;
    
        private static final String DUMMY_KEY = "dummy";
    
        public JsonOperation(String json) {
            documents = parseJson(json);
        }
    
        static final List parseJson(String json) {
            return (json.startsWith("["))
                        ? Document.parse("{\"" + DUMMY_KEY + "\": " + json + "}").getList(DUMMY_KEY, Document.class)
                        : Collections.singletonList(Document.parse(json));
        }
    
        @Override
        public Document toDocument(AggregationOperationContext context) {
            // Not necessary to return anything as we override toPipelineStages():
            return null;
        }
    
        @Override
        public List toPipelineStages(AggregationOperationContext context) {
            return documents;
        }
    }
    

    but implementation is now a bit ugly because of string manipulations. If somebody has a better idea of how to parse array of objects in more elegant way, please edit this post or drop a comment. Ideally there should be some method in Mongo core that allows to parse either JSON object or list (returns BasicDBObject/BasicDBList or Document/List).

    Also node that I have skipped the step of transforming Document instances in toPipelineStages() method as it is not necessary in my case:

    @Override
    public List toPipelineStages(AggregationOperationContext context) {
        return documents.stream().map(document -> context.getMappedObject(document)).collect(Collectors.toList());
    }
    
    

提交回复
热议问题