1

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:

  1. 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 use Aggregation.match().
  2. Depending on the context of the user, I have to return a different count of books that can be filtered based on startsWith() or like() principle. -> Here I have a problem.

Assuming that I have 4 published books, one more added by normal user and one more hidden.

  1. Administrator should know about all of them, so he will see booksCount as 6.
  2. The regular user sees only published or added by himself, so he will see booksCount as 5.
  3. Not logged users see only published, so he will see booksCount as 4.
  4. 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:

  1. How can I achieve such behavior using Spring Data MongoDB (I'll probably be able to tweak the version a little bit)?
  2. If we implement this, will I be able to apply appropriate filters to booksCount as stated in description?
  3. 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.

MattIT
  • 316
  • 2
  • 15
  • I also strongly recommend not to use @DBRef since it jas implications like slowing down. If you het rid of dbref, the process you need to achive is so easy using aggregation pipelines – varman Aug 26 '20 at 00:42
  • You can even use `$text` search with mathc stage as you mentioned above – varman Aug 26 '20 at 00:44

0 Answers0