Spring Data MongoDB: projections et agrégations

1. Vue d'ensemble

Spring Data MongoDB fournit des abstractions simples de haut niveau au langage de requête natif MongoDB. Dans cet article, nous explorerons la prise en charge du framework Projections and Aggregation.

Si vous êtes nouveau dans ce sujet, reportez-vous à notre article d'introduction Introduction à Spring Data MongoDB.

2. Projection

Dans MongoDB, les projections sont un moyen de récupérer 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 avec MongoTemplate et MongoRepository.

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 utilisant MongoTemplate

Les méthodes include () et exclude () de la classe Field 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 tiré par les cheveux , à moins explicitement exclus.

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

Par exemple, String serait null , int / Integer serait 0 et boolean / Boolean serait faux .

Ainsi, dans l'exemple ci-dessus, le champ de nom serait John , id serait nul et age serait 0.

2.2. Projections utilisant MongoRepository

Lors de l'utilisation de MongoRepositories, les champs de l' annotation @Query peuvent être définis au format JSON:

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

Le résultat serait le même que l'utilisation de MongoTemplate. La valeur = "{}" indique aucun filtre et donc tous les documents seront récupérés.

3. Agrégation

L'agrégation dans MongoDB a été conçue 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 comme entrée pour l'étape suivante. Cette capacité à appliquer des transformations et à effectuer des calculs sur les données par étapes fait de l'agrégation un outil d'analyse très puissant.

Spring Data MongoDB fournit une abstraction pour les requêtes d'agrégation natives à l'aide des trois classes Aggregation qui encapsule une requête d' agrégation , AggregationOperation qui encapsule les étapes individuelles du pipeline et AggregationResults qui est le conteneur du résultat produit par l'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 classe Aggregation , puis créez une instance d' Aggregation à l'aide de la méthode newAggregation () sur la classe Aggregation et enfin exécutez l'agrégation à l'aide de MongoTemplate :

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 que MatchOperation et ProjectionOperation implémentent AggregationOperation . Il existe des implémentations similaires pour d'autres pipelines d'agrégation. OutType est le modèle de données pour la sortie attendue.

Maintenant, nous allons regarder quelques exemples et leurs explications pour couvrir les principaux pipelines et opérateurs d'agrégation.

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 du référentiel MongoDB.

Examinons un exemple de document après l'avoir importé dans une collection appelée zips dans la base de données de test .

{ "_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éthodes statiques de la classe Aggregation 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

Ici, nous aurons trois pipelines:

  1. $ phase de groupes résumant la population de tous les codes postaux
  2. Étape de correspondance $ pour filtrer les États comptant plus de 10 millions d'habitants
  3. $ étape de tri pour trier tous les documents par ordre décroissant de population

La sortie attendue aura un champ _id comme état et un champ statePop 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 à l' état dans le modèle:

GroupOperation groupByStateAndSumPop = group("state") .sum("pop").as("statePop"); MatchOperation filterStates = match(new Criteria("statePop").gt(10000000)); SortOperation sortByPopDesc = sort(Sort.by(Direction.DESC, "statePop")); Aggregation aggregation = newAggregation( groupByStateAndSumPop, filterStates, sortByPopDesc); AggregationResults result = mongoTemplate.aggregate( aggregation, "zips", StatePopulation.class);

La classe AggregationResults implémente Iterable 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 standard MongoDB Document 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 stage pour trier les états en fonction de la population moyenne de leur ville dans l'ordre croissant
  4. Limite de $ pour obtenir le premier État avec la population moyenne la plus faible de la ville

Although it's not necessarily required, we will use an additional $project stage to reformat the document as per out StatePopulation data model.

GroupOperation sumTotalCityPop = group("state", "city") .sum("pop").as("cityPop"); GroupOperation averageStatePop = group("_id.state") .avg("cityPop").as("avgCityPop"); SortOperation sortByAvgPopAsc = sort(Sort.by(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();

In this example, we already know that there will be only one document in the result since we limit the number of output documents to 1 in the last stage. As such, we can invoke getUniqueMappedResult() to get the required StatePopulation instance.

Another thing to notice is that, instead of relying on the @Id annotation to map _id to state, we have explicitly done it in projection stage.

3.3. Get the State With Maximum and Minimum Zip Codes

For this example, we need three stages:

  1. $ group pour compter le nombre de codes postaux pour chaque état
  2. $ sort pour classer les états par 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é le document déjà fourni 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 découvert la prise en charge du cadre d'agrégation MongoDB dans Spring Data. Nous avons couvert les principales phases d'agrégation - groupe, projet, tri, limite et correspondance et avons examiné quelques exemples de ses applications pratiques. Le code source complet est disponible à l'adresse over sur GitHub.