I need to create advanced aggregation using Spring Data MongoDB having model like that:
@Getter
@Setter
@Document
public class Library {
@Id
@JsonSerialize(using = ToStringSerializer.class)
private ObjectId id;
private Address address;
private String workingHours;
...
}
@Getter
@Setter
@Document
public class Book {
@Id
@JsonSerialize(using = ToStringSerializer.class)
private ObjectId id;
private Boolean published;
private Boolean hidden;
private String title;
@DBRef
@NotNull
private Library library;
...
}
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>
Libraries collection:
{
"_id" : ObjectId("5f45440ee89590218e83a697"),
"workingHours" : "8:00 PM - 8:00 AM",
"address" : DBRef("addresses", ObjectId("5f4544198da452a5523e3d11"))
}
Books collection:
{
"_id" : ObjectId("5f454423be823729015661ed"),
"published": true,
"hidden": false,
"title": "The Hobbit, or There and Back Again"
"library" : DBRef("libraries", ObjectId("5f45440ee89590218e83a697"))
},
{
"_id" : ObjectId("5f45445b876d08649b88ed5a"),
"published": true,
"hidden": false,
"title": "Harry Potter and the Philosopher's Stone"
"library" : DBRef("libraries", ObjectId("5f45440ee89590218e83a697"))
},
{
"_id" : ObjectId("5f45446c7e33ca70363f629a"),
"published": true,
"hidden": false,
"title": "Harry Potter and the Cursed Child"
"library" : DBRef("libraries", ObjectId("5f45440ee89590218e83a697"))
},
{
"_id" : ObjectId("5f45447285f9b3e4cb8739ad"),
"published": true,
"hidden": false,
"title": "Fantastic Beasts and Where to Find Them"
"library" : DBRef("libraries", ObjectId("5f45440ee89590218e83a697"))
},
{
"_id" : ObjectId("5f45449fc121a20afa4fbb96"),
"published": false,
"hidden": false,
"title": "Universal Parks & Resorts"
"library" : DBRef("libraries", ObjectId("5f45440ee89590218e83a697"))
},
{
"_id" : ObjectId("5f4544a5f13839bbe89edb23"),
"published": false,
"hidden": true,
"title": "Ministry of Dawn"
"library" : DBRef("libraries", ObjectId("5f45440ee89590218e83a697"))
}
Features:
- User must be able to filter by each field. -> That's not the problem for me. Based on passed parameters I would create
Criteria
and useAggregation.match()
. - Depending on the context of the user, I have to return a different count of books that can be filtered based on
startsWith()
orlike()
principle. -> Here I have a problem.
Assuming that I have 4 published books, one more added by normal user and one more hidden.
- Administrator should know about all of them, so he will see
booksCount
as6
. - The regular user sees only published or added by himself, so he will see
booksCount
as5
. - Not logged users see only published, so he will see
booksCount
as4
. - There can be some other conditions in the future.
Criteria criteria = Criteria.where("_id").ne(null).and("address.city").is("Chicago");
if (!isAdministrator()) {
criteria = criteria.and("published").is(Boolean.TRUE);
}
MatchOperation matchOperation = Aggregation.match(criteria);
LookupOperation lookupOperation = LookupOperation.newLookup().from("books").localField("id").foreignField("library.$id").as("books");
UnwindOperation unwindOperation = Aggregation.unwind("books");
CountOperation countOperation = Aggregation.count().as("booksCount");
Aggregation aggregation = Aggregation.newAggregation(matchOperation, lookupOperation, unwindOperation, countOperation);
mongoTemplate.aggregate(aggregation, "libraries", Document.class).getRawResults().getInteger("booksCount");
At first I thought I would use LookupOperation
, but it doesn't work properly (it fetches all books in the collection independently from ObjectId
). Unfortunately, I ran into a problem that we can't use DBRef
in the aggregation pipeline, except in the $match
stage. Here is the description of the problem.
I read the explanation in the second post, but I don't know how to implement such advanced projection with map operation using Spring Data MongoDB. Additionally, I would like to be able to filter by this value as described in the second point.
My questions are:
- How can I achieve such behavior using Spring Data MongoDB (I'll probably be able to tweak the version a little bit)?
- If we implement this, will I be able to apply appropriate filters to
booksCount
as stated in description? - Is there another way to accomplish such behavior using another library, framework or functionality? I was thinking only about custom DTO, querying all existing libraries, looping through them and based on passed parameters query
booksCount
. But that won't solve the filtering and pagination problem.
Thank you in advance.