Search code examples
springmongodbaggregation-frameworkspring-mongospring-mongodb

Nested Group with Spring MongoDB


I need to generate a result with the number of alerts of each level for each user.

A structure similar to the following:

{
  "identitity": "59e3b9dc5a3254691f327b67",
  "alerts": [
    {
      "level": "INFO",
      "count": "3"
    },
    {
      "level": "ERROR",
      "count": "10"

     } 
  ]

}

The alert entitity has the following structure:

@Document(collection = AlertEntity.COLLECTION_NAME)
public class AlertEntity {

    public final static String COLLECTION_NAME = "alerts";

    @Id
    private ObjectId id;

    @Field
    private AlertLevelEnum level = AlertLevelEnum.INFO;

    @Field("title")
    private String title;

    @Field("payload")
    private String payload;

    @Field("create_at")
    private Date createAt = new Date();

    @Field("delivered_at")
    private Date deliveredAt;

    @Field("delivery_mode")
    private AlertDeliveryModeEnum deliveryMode = 
   AlertDeliveryModeEnum.PUSH_NOTIFICATION;

    @Field("parent")
    @DBRef
    private ParentEntity parent;

    @Field("son")
    @DBRef
    private SonEntity son;

    private Boolean delivered = Boolean.FALSE;

}

I have implemented the following method tried to project the result in a nested way. But the "Identity" field is always null and the "alerts" field is a empty collection.

@Override
    public List<AlertsBySonDTO> getAlertsBySon(List<String> sonIds) {


        TypedAggregation<AlertEntity> alertsAggregation = 
                Aggregation.newAggregation(AlertEntity.class,
                    Aggregation.group("son.id", "level").count().as("count"),
                    Aggregation.project().and("son.id").as("id")
                        .and("alerts").nested(
                                bind("level", "level").and("count")));

        // Aggregation.match(Criteria.where("_id").in(sonIds)

            AggregationResults<AlertsBySonDTO> results = mongoTemplate.
                 aggregate(alertsAggregation, AlertsBySonDTO.class);

            List<AlertsBySonDTO> alertsBySonResultsList = results.getMappedResults();

            return alertsBySonResultsList;
    }  

The result I get is the following:

{
  "response_code_name": "ALERTS_BY_SON",
  "response_status": "SUCCESS",
  "response_http_status": "OK",
  "response_info_url": "http://yourAppUrlToDocumentedApiCodes.com/api/support/710",
  "response_data": [
    {
      "identity": null,
      "alerts": []
    },
    {
      "identity": null,
      "alerts": []
    }
  ],
  "response_code": 710
}

The result DTO is as follows:

public final class AlertsBySonDTO implements Serializable {

    private static final long serialVersionUID = 1L;


    @JsonProperty("identity")
    private String id;

    @JsonProperty("alerts")
    private ArrayList<Map<String, String>> alerts;


    public AlertsBySonDTO() {
        super();
    }

    public AlertsBySonDTO(String id, ArrayList<Map<String, String>> alerts) {
        super();
        this.id = id;
        this.alerts = alerts;
    }



    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public ArrayList<Map<String, String>> getAlerts() {
        return alerts;
    }

    public void setAlerts(ArrayList<Map<String, String>> alerts) {
        this.alerts = alerts;
    }
}

What needs to be done to project the result in a nested way?

Thanks in advance


Solution

  • In aggregation framework there is an $unwind operator which will basically transform your one element collection with nested array of two elements to two separate documents with one element from this array. So you'll get:

    {
     "identitity": "59e3b9dc5a3254691f327b67",
     "alerts": {
         "level": "INFO",
         "count": "3"
       }
    

    }

    {
         "identitity": "59e3b9dc5a3254691f327b67",
         "alerts": {
             "level": "ERROR",
             "count": "10"
         } 
    }
    

    And this is where you can start your group by with count. Should be working fine.