Wie werden Array-Elemente mit Mehrfachaggregation in Spring Data MongoDB richtig gezählt?
Ich muss eine erweiterte Aggregation mit Spring Data MongoDB mit folgendem Modell erstellen:
@Getter
@Setter
@Document
public class City {
@Id
@JsonSerialize(using = ToStringSerializer.class)
private ObjectId id;
private Address description;
private String name;
...
}
@Getter
@Setter
@Document
public class Library {
@Id
@JsonSerialize(using = ToStringSerializer.class)
private ObjectId id;
private Address address;
private String workingHours;
@JsonSerialize(using = ToStringSerializer.class)
private ObjectId cityId;
...
}
@Getter
@Setter
@Document
public class Book {
@Id
@JsonSerialize(using = ToStringSerializer.class)
private ObjectId id;
private Boolean published;
private Boolean hidden;
private String title;
@JsonSerialize(using = ToStringSerializer.class)
private ObjectId libraryId;
...
}
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-mongodb</artifactId>
<version>2.2.0</version>
</dependency>
Städte Sammlung:
{
"_id" : ObjectId("5f47878c95f47e209402fe15"),
"name" : "Warsaw",
"description" : "Sample description"
}
{
"_id" : ObjectId("5f4787918b343fff4f52c270"),
"name" : "Chicago",
"description" : "Sample description"
}
Bibliothekssammlung:
{
"_id" : ObjectId("5f45440ee89590218e83a697"),
"workingHours" : "8:00 PM - 8:00 AM",
"address" : DBRef("addresses", ObjectId("5f4544198da452a5523e3d11")),
"cityId": ObjectId("5f47878c95f47e209402fe15")
},
{
"_id" : ObjectId("5f478725d1507323a80efa31"),
"workingHours" : "8:00 PM - 8:00 AM",
"address" : DBRef("addresses", ObjectId("5f4787379e72f882e4d26912")),
"cityId": ObjectId("5f47878c95f47e209402fe15")
},
{
"_id" : ObjectId("5f47872f7c4872d4983961f5"),
"workingHours" : "8:00 PM - 8:00 AM",
"address" : DBRef("addresses", ObjectId("5f47873d5ddedadb3d6ddd6e")),
"cityId": ObjectId("5f4787918b343fff4f52c270")
}
Büchersammlung:
{
"_id" : ObjectId("5f454423be823729015661ed"),
"published": true,
"hidden": false,
"title": "The Hobbit, or There and Back Again"
"libraryId": ObjectId("5f45440ee89590218e83a697")
},
{
"_id" : ObjectId("5f45445b876d08649b88ed5a"),
"published": true,
"hidden": false,
"title": "Harry Potter and the Philosopher's Stone"
"libraryId": ObjectId("5f45440ee89590218e83a697")
},
{
"_id" : ObjectId("5f45446c7e33ca70363f629a"),
"published": true,
"hidden": false,
"title": "Harry Potter and the Cursed Child"
"libraryId": ObjectId("5f45440ee89590218e83a697")
},
{
"_id" : ObjectId("5f45447285f9b3e4cb8739ad"),
"published": true,
"hidden": false,
"title": "Fantastic Beasts and Where to Find Them"
"libraryId": ObjectId("5f45440ee89590218e83a697")
},
{
"_id" : ObjectId("5f45449fc121a20afa4fbb96"),
"published": false,
"hidden": false,
"title": "Universal Parks & Resorts"
"libraryId": ObjectId("5f45440ee89590218e83a697")
},
{
"_id" : ObjectId("5f4544a5f13839bbe89edb23"),
"published": false,
"hidden": true,
"title": "Ministry of Dawn"
"libraryId": ObjectId("5f45440ee89590218e83a697")
}
Abhängig vom Kontext des Benutzers muss ich Städte mit einer Anzahl von Bibliotheken und Büchern in der Stadt zurückgeben, die nach startsWith()oder nach dem like()Prinzip gefiltert werden können.
Angenommen, ich habe 2 Bibliotheken in einer Stadt und 1 Bibliothek in der anderen.
- Ich muss Bibliotheken zuerst mit Lookup zählen und zurückgeben
librariesCount- es wird2und sein1. - Ich muss Bücher in jeder Bibliothek abrufen / nachschlagen, sie dann als 'booksCount' zählen und dann mit multiplizieren
librariesCount, um die GesamtmengebooksCountin der Stadt zu erhalten (nennen wir escityBooksCount).
Ich habe mir eine Aggregation wie diese ausgedacht:
Criteria criteria = Criteria.where("_id");
MatchOperation matchOperation = Aggregation.match(criteria);
LookupOperation lookupOperation = LookupOperation.newLookup().from("libraries").localField("_id").foreignField("cityId").as("libraries");
UnwindOperation unwindOperation = Aggregation.unwind("libraries", true);
LookupOperation secondLookupOperation = LookupOperation.newLookup().
from("books").
localField("libraryIdArray").
foreignField("libraryId").
as("books");
UnwindOperation secondUnwindOperation = Aggregation.unwind("books", true);
AggregationOperation group = Aggregation.group("_id")
.first("_id").as("id")
.first("name").as("name")
.first("description").as("description")
.push("libraries").as("libraries")
.push("books").as("books");
ProjectionOperation projectionOperation = Aggregation.project("id", "description", "name")
.and(VariableOperators.mapItemsOf(ConditionalOperators.ifNull("libraries").then(Collections.emptyList()))
.as("library").andApply(aggregationOperationContext -> {
Document document = new Document();
document.append("id", "$$library._id");
return document;
})).as("libraryIdArray")
.and(ConvertOperators.valueOf(ArrayOperators.Size.lengthOfArray(ConditionalOperators.ifNull("libraries").then(Collections.emptyList()))).convertToString()).as("librariesCount")
.and(ConvertOperators.valueOf(ArrayOperators.Size.lengthOfArray(ConditionalOperators.ifNull("books").then(Collections.emptyList()))).convertToString()).as("cityBooksCount");
Aggregation aggregation = Aggregation.newAggregation(matchOperation, lookupOperation, unwindOperation, secondLookupOperation, secondUnwindOperation, group, projectionOperation);
mongoTemplate.aggregate(aggregation, "cities", Document.class).getRawResults().get("results");
Dank der Hilfe eines Stackoverflow-Benutzers konnte ich auf die librariesCountrichtige Weise erhalten. Leider cityBooksCountimmer darauf hinweisen 0.
Ich bin mit MongoDB nicht so vertraut, aber ich weiß, dass $lookup Operationen auf Arrays möglich sind. Deshalb habe ich versucht, Bibliotheken Arrays einer Liste zuzuordnen ObjectId, aber es funktioniert nicht richtig. Wahrscheinlich mache ich etwas falsch, aber ich weiß nicht, wo das Problem liegt. Ich bekomme die richtige Anzahl von Städten mit anderen projizierten Feldern.
Kann mir jemand sagen, was ich falsch mache und wie ich es korrigieren kann?
Danke im Voraus.
Antworten
Dies könnte Ihnen die erwartete Antwort geben.
db.cities.aggregate([
{
"$lookup": { "from": "Libraries", "localField": "_id", "foreignField": "cityId", "as": "libraries" } }, { $unwind: {
path: "$libraries", preserveNullAndEmptyArrays: true } }, { "$lookup": {
"from": "Books",
"localField": "libraries._id",
"foreignField": "libraryId",
"as": "books"
}
},
{
$unwind: { path: "$books",
preserveNullAndEmptyArrays: true
}
},
{
$group: { _id: "$_id",
name: {
$first: "$name"
},
description: {
$first: "$description"
},
libraries: {
$push: "$libraries"
},
books: {
$push: "$books"
}
}
},
{
$project: { _id: 1, name: 1, description: 1, libraryCount: { $size: "$libraries" }, bookCount: { $size: "$books"
}
}
}
])
Wie wir bereits besprochen haben, gibt es einige geringfügige Änderungen. Ich hoffe, Sie haben verstanden, wie die Mongo-Abfrage in die Spring-Datenaggregation konvertiert wird.