mongodbspring-data-mongodb

Fields are returned as null in MongoDB Aggregation pagination with Spring Data Mongo


I am implementing pagination in a Spring Data Mongo project using MongoDB Aggregation. While the data is retrieved, the fields in the result are all returned as null, and I am struggling to understand why.

Environment:

Issue Description: Data is successfully stored in MongoDB, and I have set up pagination using the aggregation framework. However, the fields in the retrieved objects are all null in the results.

Code Details:

Label Class

@Document(collection = "labels")
public class Label extends BaseDocument {
    private LabelType type;
    private String path;
    private ObjectId datasetId;
    private String annotations;
    // Constructors, getters, setters...
}

Repository Code

@Repository
public class LabelRepositoryCustomImpl implements LabelRepositoryCustom {
    private final MongoTemplate mongoTemplate;
    
    @Override
    public Page<Label> findAllWithPagination(Pageable pageable, ObjectId datasetId) {
        Aggregation aggregation = Aggregation.newAggregation(
            Aggregation.match(Criteria.where("datasetId").is(datasetId)),
            Aggregation.sort(pageable.getSort()),
            Aggregation.facet(
                Aggregation.count().as("totalCount"),
                Aggregation.skip((long) pageable.getPageNumber() * pageable.getPageSize()),
                Aggregation.limit(pageable.getPageSize())
            ).as("metadata")
        );
        AggregationResults<Label> results = mongoTemplate.aggregate(aggregation, "labels", Label.class);
        List<Label> labels = results.getMappedResults();
        return PageableExecutionUtils.getPage(labels, pageable, labels::size);
    }
}

Mongo Aggregation Query

{ 
    "aggregate" : "labels", 
    "pipeline" : [
        { "$match" : { "datasetId" : { "$oid" : "672ac21827cf3f529bbb5a57"}}}, 
        { "$sort" : { "createdAt" : -1}}, 
        { "$facet" : { 
            "metadata" : [
                { "$count" : "totalCount"}, 
                { "$skip" : 0}, 
                { "$limit" : 10}
            ]
        }}
    ]
}

Problem:

Any guidance would be greatly appreciated. Thank you!


Solution

  • the problem seems that your class Label doesn't match output query. Your query return a structure like this:

    {
      metadata: [
      { totalCount: 100 }
      ]
    }
    

    and your class have a structure like this:

    {
      type: ""
      path: ""
      datasetId: ""
      annotations: ""
    }
    

    So you are losing all prev fields of documents because $facet:

    Processes multiple aggregation pipelines within a single stage on the same set of input documents. Each sub-pipeline has its own field in the output document where its results are stored as an array of documents.

    Source: https://www.mongodb.com/docs/v6.1/reference/operator/aggregation/facet/

    Can I suggest to use MongoRepository without use MongoTemplate?

    Example like this:

    import org.bson.types.ObjectId;
    import org.springframework.data.domain.Page;
    import org.springframework.data.domain.Pageable;
    import org.springframework.data.mongodb.repository.MongoRepository;
    import org.springframework.data.mongodb.repository.Query;
    
    public interface LabelRepository extends MongoRepository<Label, ObjectId> {
        
        @Query("{ 'datasetId': ?0 }")
        Page<Label> findByDatasetId(ObjectId datasetId, Pageable pageable);
    }
    

    This will return a Page automatically.

    If you want to use Aggregation with mongoTemplate you must run 2 query (one count and one for page) like this:

    @Repository
    public class LabelRepositoryCustomImpl implements LabelRepositoryCustom {
        private final MongoTemplate mongoTemplate;
        
        @Autowired
        public LabelRepositoryCustomImpl(MongoTemplate mongoTemplate) {
            this.mongoTemplate = mongoTemplate;
        }
        
        @Override
        public Page<Label> findAllWithPagination(Pageable pageable, ObjectId datasetId) {
            // Conteggio totale dei documenti
            long total = countByDatasetId(datasetId);
            
            // Aggregazione per la paginazione
            Aggregation aggregation = Aggregation.newAggregation(
                Aggregation.match(Criteria.where("datasetId").is(datasetId)),
                Aggregation.sort(pageable.getSort()),
                Aggregation.skip((long) pageable.getPageNumber() * pageable.getPageSize()),
                Aggregation.limit(pageable.getPageSize())
            );
            
            AggregationResults<Label> results = mongoTemplate.aggregate(aggregation, "labels", Label.class);
            List<Label> labels = results.getMappedResults();
    
            return new PageImpl<>(labels, pageable, total);
        }
        
        private long countByDatasetId(ObjectId datasetId) {
            // Aggregazione per il conteggio totale dei documenti
            Aggregation aggregation = Aggregation.newAggregation(
                Aggregation.match(Criteria.where("datasetId").is(datasetId)),
                Aggregation.count().as("total")
            );
            
            AggregationResults<CountResult> results = mongoTemplate.aggregate(aggregation, "labels", CountResult.class);
            CountResult countResult = results.getUniqueMappedResult();
            return countResult != null ? countResult.getTotal() : 0;
        }
        
        static class CountResult {
            private long total;
    
            public long getTotal() {
                return total;
            }
    
            public void setTotal(long total) {
                this.total = total;
            }
        }
    }
    

    a lot of code lines unnecessary imo.

    Hope this will help you