Spring Data MongoDB: Projections et agrégations

Spring Data MongoDB: Projections et agrégations

1. Vue d'ensemble

Spring Data MongoDB fournit des abstractions simples de haut niveau en langage de requête natif MongoDB. Dans cet article,we will explore the support for Projections and Aggregation framework.

Si vous êtes nouveau dans ce sujet, consultez notre article d'introductionIntroduction to Spring Data MongoDB.

2. Projection

Dans MongoDB, les projections permettent d'extraire uniquement les champs obligatoires d'un document à partir d'une base de données. Cela réduit la quantité de données à transférer du serveur de base de données au client et augmente donc les performances.

Avec Spring Data MongDB, les projections peuvent être utilisées à la fois avecMongoTemplate etMongoRepository.

Avant d'aller plus loin, examinons le modèle de données que nous utiliserons:

@Document
public class User {
    @Id
    private String id;
    private String name;
    private Integer age;

    // standard getters and setters
}

2.1. Projections utilisantMongoTemplate

Les méthodesinclude() etexclude() sur la classeField sont utilisées pour inclure et exclure des champs respectivement:

Query query = new Query();
query.fields().include("name").exclude("id");
List john = mongoTemplate.find(query, User.class);

Ces méthodes peuvent être chaînées pour inclure ou exclure plusieurs champs. Le champ marqué comme@Id (_id dans la base de données) est toujours récupéré à moins d'être explicitement exclu.

Les champs exclus sontnull dans l'instance de classe de modèle lorsque les enregistrements sont extraits avec projection. Dans le cas où les champs sont d'un type primitif ou de leur classe wrapper, la valeur des champs exclus est la valeur par défaut des types primitifs.

Par exemple,String seraitnull,int /Integer serait0 etboolean /Boolean seraitfalse.

Ainsi, dans l'exemple ci-dessus, le champname seraitJohn,id seraitnull etage serait0.

2.2. Projections utilisantMongoRepository

Lors de l'utilisation de MongoRepositories, l'annotationfields de@Query peut être définie au format JSON:

@Query(value="{}", fields="{name : 1, _id : 0}")
List findNameAndExcludeId();

Le résultat serait identique à l'utilisation de MongoTemplate. Levalue=”\{}” ne désigne aucun filtre et donc tous les documents seront récupérés.

3. Aggregation

L'agrégation dans MongoDB a été conçu pour traiter les données et renvoyer les résultats calculés. Les données sont traitées par étapes et la sortie d'une étape est fournie en entrée de l'étape suivante. Cette possibilité d'appliquer des transformations et d'effectuer des calculs sur des données par étapes fait de l'agrégation un outil très puissant pour l'analyse.

Spring Data MongoDB fournit une abstraction pour les requêtes d'agrégation natives à l'aide des trois classesAggregation qui encapsule une requête d'agrégation,AggregationOperation qui encapsule les étapes individuelles du pipeline etAggregationResults qui est le conteneur du résultat produit par agrégation.

Pour effectuer et agréger, commencez par créer des pipelines d'agrégation à l'aide des méthodes de générateur statiques sur la classeAggregation, puis créez une instance deAggregation en utilisant la méthodenewAggregation() sur la classeAggregation et enfin exécutez l'agrégation en utilisantMongoTemplate:

MatchOperation matchStage = Aggregation.match(new Criteria("foo").is("bar"));
ProjectionOperation projectStage = Aggregation.project("foo", "bar.baz");

Aggregation aggregation
  = Aggregation.newAggregation(matchStage, projectStage);

AggregationResults output
  = mongoTemplate.aggregate(aggregation, "foobar", OutType.class);

Veuillez noter queMatchOperation etProjectionOperation implémententAggregationOperation. Des implémentations similaires existent pour d'autres pipelines d'agrégation. OutType est le modèle de données pour la sortie attendue.

Nous allons maintenant examiner quelques exemples et leurs explications pour couvrir les principaux pipelines et opérateurs de regroupement.

L'ensemble de données que nous utiliserons dans cet article répertorie les détails de tous les codes postaux aux États-Unis qui peuvent être téléchargés à partir deMongoDB repository.

Examinons un exemple de document après l'avoir importé dans une collection appeléezips dans la base de donnéestest.

{
    "_id" : "01001",
    "city" : "AGAWAM",
    "loc" : [
        -72.622739,
        42.070206
    ],
    "pop" : 15338,
    "state" : "MA"
}

Par souci de simplicité et pour rendre le code concis, dans les extraits de code suivants, nous supposerons que toutes les méthodesstatic de la classeAggregation sont importées statiquement.

3.1. Obtenez tous les États avec une population supérieure à 10 millions d'habitants par ordre décroissant de population

Nous aurons ici trois pipelines:

  1. $group étape résumant la population de tous les codes postaux

  2. Étape$match pour filtrer les États avec une population de plus de 10 millions d'habitants

  3. Étape$sort pour trier tous les documents par ordre décroissant de population

La sortie attendue aura un champ_id comme état et un champstatePop avec la population totale de l'état. Créons un modèle de données pour cela et exécutons l'agrégation:

public class StatePoulation {

    @Id
    private String state;
    private Integer statePop;

    // standard getters and setters
}

L'annotation@Id mappera le champ_id de la sortie versstate dans le modèle:

GroupOperation groupByStateAndSumPop = group("state")
  .sum("pop").as("statePop");
MatchOperation filterStates = match(new Criteria("statePop").gt(10000000));
SortOperation sortByPopDesc = sort(new Sort(Direction.DESC, "statePop"));

Aggregation aggregation = newAggregation(
  groupByStateAndSumPop, filterStates, sortByPopDesc);
AggregationResults result = mongoTemplate.aggregate(
  aggregation, "zips", StatePopulation.class);

La classeAggregationResults implémenteIterable et nous pouvons donc la parcourir et imprimer les résultats.

Si le modèle de données de sortie n'est pas connu, la classe MongoDB standardDocument peut être utilisée.

3.2. Obtenir le plus petit État par population moyenne de la ville

Pour ce problème, nous aurons besoin de quatre étapes:

  1. $group pour additionner la population totale de chaque ville

  2. $group pour calculer la population moyenne de chaque état

  3. $sort étape pour classer les états en fonction de la population moyenne de leur ville dans l'ordre croissant

  4. $limit pour obtenir le premier état avec la population moyenne de la ville la plus faible

Bien que cela ne soit pas nécessairement obligatoire, nous utiliserons une étape supplémentaire$project pour reformater le document selon le modèle de données deStatePopulation.

GroupOperation sumTotalCityPop = group("state", "city")
  .sum("pop").as("cityPop");
GroupOperation averageStatePop = group("_id.state")
  .avg("cityPop").as("avgCityPop");
SortOperation sortByAvgPopAsc = sort(new Sort(Direction.ASC, "avgCityPop"));
LimitOperation limitToOnlyFirstDoc = limit(1);
ProjectionOperation projectToMatchModel = project()
  .andExpression("_id").as("state")
  .andExpression("avgCityPop").as("statePop");

Aggregation aggregation = newAggregation(
  sumTotalCityPop, averageStatePop, sortByAvgPopAsc,
  limitToOnlyFirstDoc, projectToMatchModel);

AggregationResults result = mongoTemplate
  .aggregate(aggregation, "zips", StatePopulation.class);
StatePopulation smallestState = result.getUniqueMappedResult();

Dans cet exemple, nous savons déjà qu'il n'y aura qu'un seul document dans le résultat puisque nous limitons le nombre de documents de sortie à 1 lors de la dernière étape. En tant que tel, nous pouvons invoquergetUniqueMappedResult() pour obtenir l'instanceStatePopulation requise.

Une autre chose à noter est qu'au lieu de nous fier à l'annotation@Id pour mapper_id à l'état, nous l'avons fait explicitement en phase de projection.

3.3. Obtenez l'état avec les codes postaux maximum et minimum

Pour cet exemple, nous avons besoin de trois étapes:

  1. $group pour compter le nombre de codes postaux pour chaque état

  2. $sort pour classer les états par le nombre de codes postaux

  3. $group pour trouver l'état avec les codes postaux max et min en utilisant les opérateurs$first et$last

GroupOperation sumZips = group("state").count().as("zipCount");
SortOperation sortByCount = sort(Direction.ASC, "zipCount");
GroupOperation groupFirstAndLast = group().first("_id").as("minZipState")
  .first("zipCount").as("minZipCount").last("_id").as("maxZipState")
  .last("zipCount").as("maxZipCount");

Aggregation aggregation = newAggregation(sumZips, sortByCount, groupFirstAndLast);

AggregationResults result = mongoTemplate
  .aggregate(aggregation, "zips", Document.class);
Document document= result.getUniqueMappedResult();

Ici, nous n'avons utilisé aucun modèle mais utilisé lesDocument déjà fournis avec le pilote MongoDB.

4. Conclusion

Dans cet article, nous avons appris à récupérer des champs spécifiés d'un document dans MongoDB à l'aide de projections dans Spring Data MongoDB.

Nous avons également entendu parler du support de la structure d'agrégation MongoDB dans Spring Data. Nous avons couvert les principales phases d'agrégation - regrouper, projeter, trier, limiter et mettre en correspondance et examiner quelques exemples d'applications pratiques. Le code source complet estavailable over on GitHub.