Logo

dev-resources.site

for different kinds of informations.

Spring Data & MongoDB — Pagination+Sorting w/ Aggregation and Lookup

Published at
12/12/2023
Categories
springboot
mongodb
springdata
Author
williamspader
Categories
3 categories in total
springboot
open
mongodb
open
springdata
open
Author
13 person written this
williamspader
open
Spring Data & MongoDB — Pagination+Sorting w/ Aggregation and Lookup

Problem: Need to perform pagination+sorting with nullable filters and properties from two collections.

One collection holds the ID of the document from another collection that has the property we need to retrieve.

For those using reactive mongodb, the ReactiveMongoRepository only extends from ReactiveSortingRepository and we also want the following behavior:

a: { otherCollectionId: 1, info: 2}
b: { otherCollectionId: 1, info: 3}
c: { otherCollectionId: 2, info: 1}
findByOtherCollectionIdAndInfo(1, 2) //returns document a
findByOtherCollectionIdAndInfo(1, null) //returns documents a and b
Enter fullscreen mode Exit fullscreen mode

Solution

Consider you have the following two collections where collection A has the id from collection B through otherCollectionId property.

Collection A
{
  "_id": ObjectId("..."),
  "otherCollectionId": "...",
  "info": "Some info",
  "anotherInfo": "Some other info"
}
Enter fullscreen mode Exit fullscreen mode
Collection B
{
  "_id": ObjectId("..."),
  "label": "Some label"
}
Enter fullscreen mode Exit fullscreen mode

Your API needs to respond to the client containingcollectionA infos + the label property from collectionB, and it must be paginated with the possibility of sorting through the label property.

To get the chunk of data, we can use the following code

mongoTemplate.aggregate(aggregation, "collectionA", Result.class)
return Aggregation.newAggregation(
  Aggregation.match(getCriteria(filter)),
  projectionOperation,
  getLookupOperation(),
  getUnwindOperation(),
  addLabelField(),
  projectionOperationWithLabel,
  Aggregation.sort(pageable.getSort()),
  Aggregation.skip((long) pageable.getPageNumber() * pageable.getPageSize()),
  Aggregation.limit(pageable.getPageSize())
);
Enter fullscreen mode Exit fullscreen mode

The getCriteria(filter) method may be

private Criteria getCriteria(BusinessObject.Filter filter) {
  List<Criteria> criterias = new ArrayList<>();

  Optional.ofNullable(filter.getInfo())
    .ifPresent(info-> criterias.add(Criteria.where("info").is(info)));

  Optional.ofNullable(filter.getOtherCollectionId())
    .ifPresent(otherCollectionId -> criterias.add(Criteria.where("otherCollectionId").is(otherCollectionId)));

  return new Criteria().andOperator(
    criterias.toArray(Criteria[]::new)
  );
}
Enter fullscreen mode Exit fullscreen mode

The projectionOperation may be

ProjectionOperation projectionOperation = Aggregation.project(
  "info", "anotherInfo"
).and(ConvertOperators.ToObjectId.toObjectId("$otherCollectionId")).as("convertedId");
Enter fullscreen mode Exit fullscreen mode

In the project operation above, we need to convert otherCollectionId property to ObjectId before the lookup, so we can compare same data type.

Then, getLookupOperation() method may be

private LookupOperation getLookupOperation() {
  return LookupOperation.newLookup()
          .from("collectionB")
          .localField("convertedId")
          .foreignField("_id")
          .as("joinedData");
}
Enter fullscreen mode Exit fullscreen mode

The convertedId property is the otherCollectionId but as ObjectId.

Then, depending on the situation, you can flat the resulting array from the lookup operation like this

private UnwindOperation getUnwindOperation() {
  return Aggregation.unwind("$joinedData");
}
Enter fullscreen mode Exit fullscreen mode

Now, we add the label property from the collectionB

private AddFieldsOperation addLabelField() {
  return AddFieldsOperation.addField("label").withValue("$joinedData.label").build();
}
Enter fullscreen mode Exit fullscreen mode

Then, we can perform another project operation containing the label property and remove the joinedData to get only the data we need. We also perform another conversion to get otherCollectionId property.

ProjectionOperation projectionOperation = Aggregation.project(
  "info", "anotherInfo", "label"
).and(ConvertOperators.ToObjectId.toObjectId("$convertedId")).as("otherCollectionId");
Enter fullscreen mode Exit fullscreen mode

Finally, if you need the total count to return something like PageImpl from the API, you can perform a count query on the collectionA.

Then, you’ll have something like this to return from the repository, where it.getT1() is the list of items and it.getT2() is the total of elements:

return new PageImpl<>(it.getT1(), pageable, it.getT2())
Enter fullscreen mode Exit fullscreen mode

Hope this article helps!

springdata Article's
30 articles in total
Favicon
Handling Asynchronous Execution with Transactions in Spring: A Common Pitfall and How to Solve It
Favicon
Streamlined Java Data Querying
Favicon
Jakarta Persistence API (JPA) example application: Northwind sample database
Favicon
Spring Data JPA: About Specification interface
Favicon
What is implementation class of org.springframework.data.jpa.repository.JpaRepository interface?
Favicon
Spring Data JPA demonstration
Favicon
Spring Data & MongoDB — Pagination+Sorting w/ Aggregation and Lookup
Favicon
Demystifying Spring Data and Spring Data JPA: A Question-Based Guide: Simplify Your Data Access Layer
Favicon
How to Insert Records in Batch with Spring Data
Favicon
Um olhar sobre PagingAndSortingRepository
Favicon
Spring Data JPA Interview Questions and Answers
Favicon
Quick Tip: Kotlin, Spring Data, and Optionals
Favicon
Safely Evolving Database with Liquibase, Spring Data, and Spring Boot
Favicon
How to use Spring Data DynamoDB example | Spring Boot
Favicon
How to start SpringBoot ElasticSearch using Spring Data
Favicon
Apache Solr – How to start Spring Data Solr with SpringBoot
Favicon
Spring Boot + Angular 6 example | Spring Data + REST + MongoDb CRUD example
Favicon
SpringData Reactive Cassandra Repositories | SpringBoot
Favicon
Spring Data Rest – How to create a RestAPIs with JPA Many-to-Many relational entities
Favicon
SpringData DynamoDB CRUD Example | SpringBoot
Favicon
Spring Data Rest – How to create a RestAPIs with JPA One-to-Many relational entities | SpringBoot + MySql + HAL Browser
Favicon
How to access MySQL database with Spring Data Rest application
Favicon
Kotlin Spring Boot + Angular 6 CRUD + PostgreSQL example | Spring Data JPA + REST APIs example
Favicon
Spring Data Redis Messaging – PubSub example | Spring Boot
Favicon
Spring Data Redis CRUD Operations example with Spring Boot
Favicon
Your own custom Spring Data repository
Favicon
SpringData MongoDB GridFsTemplate to save, retrieve, delete binary files (Image, Text files)
Favicon
How to use SpringData MongoRepository to interact with MongoDB
Favicon
How to start Spring Data Cassandra with SpringBoot
Favicon
Angular + Spring WebFlux + Spring Data Reactive Cassandra example

Featured ones: