Home > OS >  How to use $facet , $addFields and $function in Spring-Data-MongoDB
How to use $facet , $addFields and $function in Spring-Data-MongoDB

Time:07-10

I'm developing a method in Spring-Data-MongoDB that use $facet,$addFields and $function but it return null.

This is the aggregation in MongoDB

db.utenti.aggregate([
    {
        $facet:
        {
            "aggregation": [
                  {
                    $match: { age: { $gt: 18 } }
                },
                {
                    $addFields:{
                        max: {
                            $function: {
                               body: function(name, scores) {
                                  let max = Math.max(...scores);
                                  return `Hello ${name}.  Your max score is ${max}.`
                               },
                               args: [ "$name", "$scores"],
                               lang: "js"
                            }    
                        }
                      
                    }
                }
            ]
        }
    }
]).pretty()

This the expected result

{
        "aggregation" : [
                {
                        "_id" : ObjectId("62c5f16b39b3d635ab6bf60d"),
                        "username" : "james34",
                        "name" : "james",
                        "role" : [
                                "user",
                                "specialuser"
                        ],
                        "age" : 34,
                        "scores" : [
                                10,
                                9,
                                10
                        ],
                        "_class" : "com.project.ecommercemongo.model.User",
                        "max" : "Hello james.  Your max score is 10."
                },
                {
                        "_id" : ObjectId("62c5f1b839b3d635ab6bf60e"),
                        "username" : "elizabeth54",
                        "name" : "elizabeth",
                        "role" : [
                                "user",
                                "specialuser"
                        ],
                        "age" : 54,
                        "scores" : [
                                10,
                                10,
                                10
                        ],
                        "_class" : "com.project.ecommercemongo.model.User",
                        "max" : "Hello elizabeth.  Your max score is 10."
                },
                {
                        "_id" : ObjectId("62c5f1f139b3d635ab6bf60f"),
                        "username" : "frank50",
                        "name" : "frank",
                        "role" : [
                                "user"
                        ],
                        "age" : 50,
                        "scores" : [
                                10,
                                10,
                                10
                        ],
                        "_class" : "com.project.ecommercemongo.model.User",
                        "max" : "Hello frank.  Your max score is 10."
                },
                {
                        "_id" : ObjectId("62c5f27a39b3d635ab6bf610"),
                        "username" : "john26",
                        "name" : "john",
                        "role" : [
                                "user"
                        ],
                        "age" : 26,
                        "scores" : [
                                8,
                                8,
                                10
                        ],
                        "_class" : "com.project.ecommercemongo.model.User",
                        "max" : "Hello john.  Your max score is 10."
                }
        ]
}

This is the result I get

[
    {
        "_id": null,
        "username": null,
        "name": null,
        "role": null,
        "age": null,
        "scores": null
    }
]

this is the method that call facet addFields and function,but it don't add the field "max" and returns null

public List<User> aggregation1() {
        List<Object> bl = new LinkedList<Object>();
        bl.add("$name");
        bl.add("$scores");
        ScriptOperators.Function function= Function.function("function(name, scores) {let max = Math.max(...scores); return `Hello ${name}.  Your max score is ${max}`}").args(bl).lang("js");//`Hello ${name}.  Your max score is ${max}.`}");
        System.out.println(function.toDocument());
        FacetOperation facetOperation1 = Aggregation.facet(Aggregation.match(Criteria.where("age").gte(18)),Aggregation.addFields().addFieldWithValue("max", function).build()).as("aggregation");
        Aggregation agg = Aggregation.newAggregation(facetOperation1);
        return mongoTemplate.aggregate(agg, "utenti",User.class).getMappedResults();
    }

And this is the User class

@Document(collection="utenti")
//@Sharded(shardKey = { "country", "userId" }) 
public class User {

    @Id
    private String _id;
    private String username;
    private String name;
    private String[] role;
    private Integer age;
    private Integer[] scores;
   


    public User() {
        super();
    }

    public String get_id() {
        return _id;
    }

    public void set_id(String _id) {
        this._id = _id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String[] getRole() {
        return role;
    }

    public void setRole(String[] role) {
        this.role = role;
    }
    
    public Integer[] getScores() {
        return scores;
    }

    public void setScores(Integer[] scores) {
        this.scores = scores;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    
}

how can i put all the users into the facet and add a field ? Thank you

Update Now it work, I had to map the output with the aggregation fields

public List<MaxFacet> aggregation1() {

        List<Object> bl = new LinkedList<Object>();
        bl.add("$name");
        bl.add("$scores");
        ScriptOperators.Function function = Function.function(
                "function(name, scores) {let max = Math.max(...scores); return `Hello ${name}.  Your max score is ${max}.`}")
                .args(bl).lang("js");// `Hello ${name}. Your max score is ${max}.`}");
        System.out.println(function.toDocument());
        FacetOperation facetOperation = Aggregation.facet().and(match(Criteria.where("age").gte(18)),
                Aggregation.addFields().addFieldWithValue("max", function).build()).as("aggregation");
        Aggregation agg = Aggregation.newAggregation(facetOperation);

        return mongoOperations.aggregate(agg, mongoTemplate.getCollectionName(User.class), MaxFacet.class)
                .getMappedResults();

    }
public class MaxFacet {
    
        private List<UserOut> aggregation;

        public List<UserOut> getAggregation() {
            return aggregation;
        }
        public void setAggregation(List<UserOut> facet1) {
            this.aggregation = facet1;
        }
        public MaxFacet() {
            
            // TODO Auto-generated constructor stub
        }
}
@JsonPropertyOrder({"id","username","name","age","scores","max"})
public class UserOut {
    @JsonProperty(value="id")
    private String id;
    private String username;
    private String name;
    private String []scores;
    private String max;
    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }
    
    public String getUsername() {
        return username;
    }
    public void setUsername(String username) {
        this.username = username;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String[] getScores() {
        return scores;
    }
    public void setScores(String[] scores) {
        this.scores = scores;
    }
    public String getMax() {
        return max;
    }
    public void setMax(String value) {
        this.max= value;
    }
    
}
[
    {
        "aggregation": [
            {
                "id": "62c5f16b39b3d635ab6bf60d",
                "username": "james34",
                "name": "james",
                "scores": [
                    "10",
                    "9",
                    "10"
                ],
                "max": "Hello james.  Your max score is 10."
            },
            {
                "id": "62c5f1b839b3d635ab6bf60e",
                "username": "elizabeth54",
                "name": "elizabeth",
                "scores": [
                    "10",
                    "10",
                    "10"
                ],
                "max": "Hello elizabeth.  Your max score is 10."
            },
            {
                "id": "62c5f1f139b3d635ab6bf60f",
                "username": "frank50",
                "name": "frank",
                "scores": [
                    "10",
                    "10",
                    "10"
                ],
                "max": "Hello frank.  Your max score is 10."
            },
            {
                "id": "62c5f27a39b3d635ab6bf610",
                "username": "john26",
                "name": "john",
                "scores": [
                    "8",
                    "8",
                    "10"
                ],
                "max": "Hello john.  Your max score is 10."
            }
        ]
    }
]

CodePudding user response:

Alternative soluition: You can simplify your code this way

1- Use inheritance mecanism for the UserOut class

public class UserOut extends User {
    private String max;

    public String getMax() {
        return max;
    }
    public void setMax(String value) {
        this.max= value;
    }
}

2- For the complex pipelines, the custom AggregationOperation implementation is friendlier than Spring Data builder.

AggregationOperation facetOperation = ao -> 
    new Document("$facet", 
        new Document("aggregation", Arrays.asList(
            new Document("$match", new Document("age", new Document("$gt", 18))),
            new Document("$addFields", 
                new Document("max", 
                    new Document("$function", 
                        new Document("body", "function(name, scores) {" 
                                             "  let max = Math.max(...scores);" 
                                             "  return `Hello ${name}.  Your max score is ${max}.`" 
                                             "}")
                            .append("args", Arrays.asList("$name", "$scores"))
                            .append("lang", "js")
                    )
                )
            )
        ))
    );

Aggregation agg = Aggregation.newAggregation(facetOperation);
return mongoOperations
    .aggregate(agg, mongoTemplate.getCollectionName(User.class), MaxFacet.class)
    .getMappedResults();

Alternatively, just parse your shell JSON query this way (triple quotes starting from Java 13):

Document.parse("""
{
    "$facet":
    {
        "aggregation": [
              {
                "$match": { "age": { "$gt": 18 } }
            },
            {
                "$addFields":{
                    "max": {
                        "$function": {
                           "body": function(name, scores) {
                              let max = Math.max(...scores);
                              return `Hello ${name}.  Your max score is ${max}.`
                           },
                           "args": [ "$name", "$scores"],
                           "lang": "js"
                        }    
                    }
                  
                }
            }
        ]
    }
}
""")
  • Related