Spring Data MongoDB: Projektionen und Aggregationen

Spring Data MongoDB: Projektionen und Aggregationen

1. Überblick

Spring Data MongoDB bietet einfache Abstraktionen auf hoher Ebene für die native Abfragesprache MongoDB. In diesem Artikel werdenwe will explore the support for Projections and Aggregation framework.

Wenn Sie mit diesem Thema noch nicht vertraut sind, lesen Sie unseren EinführungsartikelIntroduction to Spring Data MongoDB.

2. Projektion

In MongoDB sind Projektionen eine Möglichkeit, nur die erforderlichen Felder eines Dokuments aus einer Datenbank abzurufen. Dies reduziert die Datenmenge, die vom Datenbankserver zum Client übertragen werden muss, und erhöht somit die Leistung.

Mit Spring Data MongDB können Projektionen sowohl mitMongoTemplate als auch mitMongoRepository. verwendet werden

Bevor wir fortfahren, schauen wir uns das Datenmodell an, das wir verwenden werden:

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

    // standard getters and setters
}

2.1. Projektionen mitMongoTemplate

Die Methodeninclude() undexclude() für die KlasseField werden verwendet, um Felder einzuschließen bzw. auszuschließen:

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

Diese Methoden können verkettet werden, um mehrere Felder ein- oder auszuschließen. Das in der Datenbank als@Id (_id) gekennzeichnete Feld wird immer abgerufen, sofern dies nicht ausdrücklich ausgeschlossen ist.

Ausgeschlossene Felder sindnull in der Modellklasseninstanz, wenn Datensätze mit Projektion abgerufen werden. Wenn Felder eines primitiven Typs oder ihrer Wrapper-Klasse sind, sind die Werte der ausgeschlossenen Felder Standardwerte der primitiven Typen.

Zum Beispiel wäreStringnull,int /Integer wäre0 undboolean /Boolean wärefalse.

Somit wäre im obigen Beispiel das FeldnameJohn,id wärenull undage wäre0.

2.2. Projektionen mitMongoRepository

Bei Verwendung von MongoRepositories kann die Annotationfields von@Query im JSON-Format definiert werden:

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

Das Ergebnis wäre dasselbe wie mit der MongoTemplate. Dasvalue=”\{}” bedeutet keine Filter und daher werden alle Dokumente abgerufen.

3. Aggregation

Die Aggregation in MongoDB wurde erstellt, um Daten zu verarbeiten und berechnete Ergebnisse zurückzugeben. Die Daten werden in Stufen verarbeitet und die Ausgabe einer Stufe wird als Eingabe für die nächste Stufe bereitgestellt. Diese Fähigkeit, Transformationen anzuwenden und Daten schrittweise zu berechnen, macht die Aggregation zu einem sehr leistungsstarken Tool für die Analyse.

Spring Data MongoDB bietet eine Abstraktion für native Aggregationsabfragen unter Verwendung der drei KlassenAggregation, die eine Aggregationsabfrage umschließen,AggregationOperation, die einzelne Pipeline-Stufen umschließt, undAggregationResults, die der Container des von erzeugten Ergebnisses ist Anhäufung.

Um eine Aggregation durchzuführen, erstellen Sie zunächst Aggregationspipelines mit den statischen Builder-Methoden für die KlasseAggregationund anschließend eine Instanz vonAggregation mit der MethodenewAggregation()für die KlasseAggregationund Führen Sie schließlich die Aggregation mitMongoTemplate aus:

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);

Bitte beachten Sie, dass sowohlMatchOperation als auchProjectionOperationAggregationOperation implementieren. Es gibt ähnliche Implementierungen für andere Aggregationspipelines. OutType ist das Datenmodell für die erwartete Ausgabe.

Im Folgenden werden einige Beispiele und Erklärungen zu den wichtigsten Aggregationspipelines und -operatoren aufgeführt.

Der Datensatz, den wir in diesem Artikel verwenden, enthält Details zu allen Postleitzahlen in den USA, die vonMongoDB repository heruntergeladen werden können.

Schauen wir uns ein Beispieldokument an, nachdem wir es in eine Sammlung mit dem Namenzips in der Datenbanktestimportiert haben.

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

Der Einfachheit halber und um den Code präziser zu gestalten, wird in den nächsten Codefragmenten davon ausgegangen, dass allestatic-Methoden derAggregation-Klasse statisch importiert werden.

3.1. Erhalten Sie alle Staaten mit einer Bevölkerung von mehr als 10 Millionen Bestellungen nach absteigender Bevölkerung

Hier werden wir drei Pipelines haben:

  1. $group Stadium, in dem die Bevölkerung aller Postleitzahlen zusammengefasst wird

  2. $match Stadium, um Staaten mit mehr als 10 Millionen Einwohnern herauszufiltern

  3. $sort Stufe, um alle Dokumente in absteigender Reihenfolge der Bevölkerung zu sortieren

Die erwartete Ausgabe hat ein Feld_id als Zustand und ein FeldstatePop mit der gesamten Staatsbevölkerung. Erstellen wir hierfür ein Datenmodell und führen die Aggregation aus:

public class StatePoulation {

    @Id
    private String state;
    private Integer statePop;

    // standard getters and setters
}

Die Annotation@Id ordnet das Feld_id von der Ausgabestate im Modell zu:

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);

Die KlasseAggregationResults implementiertIterable, und daher können wir darüber iterieren und die Ergebnisse drucken.

Wenn das Ausgabedatenmodell nicht bekannt ist, kann die Standard-MongoDB-KlasseDocumentverwendet werden.

3.2. Erhalten Sie den kleinsten Staat nach durchschnittlicher Stadtbevölkerung

Für dieses Problem benötigen wir vier Stufen:

  1. $group, um die Gesamtbevölkerung jeder Stadt zu summieren

  2. $group zur Berechnung der Durchschnittsbevölkerung jedes Staates

  3. $sort Stadium, um Staaten nach ihrer durchschnittlichen Stadtbevölkerung in aufsteigender Reihenfolge zu ordnen

  4. $limit, um den ersten Staat mit der niedrigsten durchschnittlichen Stadtbevölkerung zu erhalten

Obwohl dies nicht unbedingt erforderlich ist, verwenden wir eine zusätzliche$project-Stufe, um das Dokument gemäß dem Datenmodell von outStatePopulationneu zu formatieren.

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();

In diesem Beispiel wissen wir bereits, dass das Ergebnis nur ein Dokument enthält, da wir die Anzahl der Ausgabedokumente in der letzten Stufe auf 1 begrenzen. Daher können wirgetUniqueMappedResult() aufrufen, um die erforderlicheStatePopulation-Instanz abzurufen.

Eine andere bemerkenswerte Sache ist, dass wir uns nicht auf die Annotation@Idverlassen, um_id zuzuordnen, sondern dies explizit in der Projektionsphase getan haben.

3.3. Holen Sie sich den Staat mit maximalen und minimalen Postleitzahlen

Für dieses Beispiel benötigen wir drei Stufen:

  1. $group, um die Anzahl der Postleitzahlen für jeden Staat zu zählen

  2. $sort, um die Zustände nach der Anzahl der Postleitzahlen zu ordnen

  3. $group, um den Status mit den Postleitzahlen max und min mithilfe der Operatoren$first und$last zu ermitteln

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();

Hier haben wir kein Modell verwendet, sondern dieDocument, die bereits im MongoDB-Treiber enthalten sind.

4. Fazit

In diesem Artikel haben wir gelernt, wie bestimmte Felder eines Dokuments in MongoDB mithilfe von Projektionen in Spring Data MongoDB abgerufen werden.

Wir haben auch die Unterstützung des MongoDB-Aggregations-Frameworks in Spring Data kennengelernt. Wir haben die wichtigsten Aggregationsphasen - Gruppieren, Projektieren, Sortieren, Begrenzen und Anpassen - behandelt und einige Beispiele für deren praktische Anwendung betrachtet. Der vollständige Quellcode istavailable over on GitHub.