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!
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