javamongodb

How to do aggregate queries with accumulators using the MongoDB driver in Java


I'm quite new to MongoDB and its interaction with Java. I'm using this driver

<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongo-java-driver</artifactId>
<version>3.4.2</version>
</dependency>

and I want to perform this aggregation query:

db.getCollection('COLLECTION').aggregate(
[
  {"$match": {"val.elem.0001": {"$exists":true}}},
  {"$project": {"FIELD_PATH": "$val.elem.0001"}},
  {$group: {_id:{"FIELD": {"$literal": "0001"}},
  "PATH": {"$addToSet": "$FIELD_PATH"}}}
]                                          
);

The Java code I've written is the following, however I'm not sure if I correctly used the addToSet method:

AggregateIterable<Document> output = collection.aggregate(Arrays.asList(
new Document("$match", new Document("val.elem.0001",new Document("$exists",true))),
new Document("$project", new Document("FIELD_PATH","$val.elem.0001")),
new Document("$group", new Document("_id",new Document("FIELD", new Document("$literal", "0001"))
    .append("PATH", Accumulators.addToSet("$PATH", "$FIELD_PATH"))))));

It is correct? Because I can't print the result on screen if I add the "append" part. The error that returns is Can't find a codec for class com.mongodb.client.model.BsonField.

So, to summarize and make more readable and comprehensive what I've asked:


Solution

  • Your usage of the API is incorrect.

    Change your aggregation to the one below (stick with Document for expressions).

    AggregateIterable<Document> output = collection.aggregate(Arrays.asList(
           new Document("$match", new Document("val.elem.0001", new Document("$exists", true))),
           new Document("$project", new Document("FIELD_PATH", "$val.elem.0001")),
           new Document("$group", new Document("_id", new Document("FIELD", new Document("$literal", "0001"))).append("PATH", new Document("$addToSet", "$FIELD_PATH")))));
    

    BsonField is a helper class primarily built to providea data holder for Accumulators which return key and Bson value pair. So it is not meant to be used as value type. When used with helper methods, it is converted into Document and serialized using the document codec.

    You can rework your aggregation to use helper methods (Filters, Projections Accumulators & Aggregates).

    AggregateIterable<Document> output = collection.aggregate(Arrays.asList(
           Aggregates.match(Filters.exists("val.elem.0001")),
           Aggregates.project(Projections.computed("FIELD_PATH", "$val.elem.0001")),
           Aggregates.group( new Document("FIELD", new Document("$literal", "0001")), Accumulators.addToSet("PATH", "$FIELD_PATH"))));
    

    You can further simplify the aggregation by using static imports.

    import static com.mongodb.client.model.Accumulators.addToSet;
    import static com.mongodb.client.model.Aggregates.group;
    import static com.mongodb.client.model.Aggregates.match;
    import static com.mongodb.client.model.Aggregates.project;
    import static com.mongodb.client.model.Projections.computed;
    import static java.util.Arrays.*;
    
    AggregateIterable<Document> output = collection.aggregate(asList(
           match(Filters.exists("val.elem.0001")),
           project(computed("FIELD_PATH", "$val.elem.0001")),
           group( new Document("FIELD", new Document("$literal", "0001")), addToSet("PATH", "$FIELD_PATH"))));
    

    See the documentation for more information.