skip to Main Content

We have a document collection that has varying attributes, lets just call them Entities. Currently we are using Spring Data Mongo Aggregation API to build up our aggregation. Essentially, this is what we are doing:

public class EntityRepository<Entity, String> extends SimpleMongoRepository {

    public Page<Entity> findEntities(Pageable pageable, Map<String, Pattern> filters) {
        // ... handle empty/null filters and paging


        // build up criteria from provided filters
        Criteria criteria = new Criteria()
        for (Entry<String, Pattern> filter : filters.entrySet()) {
            criteria = criteria.and(filter.getKey).regex(filter.getValue);
        }

        Aggregation aggregation = Aggregation.newAggregation(
            match(criteria),
            sort(sort),
            skip(pageable.getOffset()),
            limit(pageable.getPageSize())
        );

        // ... run aggregation using the MongoOperations
        // ... return using PageableExecutionUtils
        
    }

}

Unfortunately we cannot know ahead of time what filters may be coming in. Therefore the query would have to be dynamic.

What I would like to have is the following. Is this even possible? I would imagine it would have to iterate through the map of filters param.

public interface EntityRepository<Entity, String> extends MongoRepository {

    // TODO have a match conditional for each key / value pair in filters param
    @Aggregation(pipeline = {
       "{ $match : { $and: [] } } "
    })
    @Meta(allowDiskUse = true)
    public Page<Entity> findEntities(Pageable pageable, Map<String, Pattern> filters);

}

2

Answers


  1. Use MongoTemplate or MongoOperations instead of aggregation annotation.
    Inject one of them and make your queries with your filters map.

    List<Criteria> criterias = new ArrayList();
    
    //create your queries from your map something like
    //criterias.add(where(entry.getKey()).is(entry.getValue()));
    
    
    mongoOperations.find(new Criteria().andOperator(criterias));
    
    Login or Signup to reply.
  2. Yes, it is possible to dynamically generate the $match stage in your aggregation pipeline based on the keys and values in the filters parameter. You can achieve this by using the AggregationSpELExpression annotation in Spring Data MongoDB.

    Here’s an example of how you can modify your EntityRepository interface:

    public interface EntityRepository<Entity, String> extends MongoRepository<Entity, String> {
    
        @Aggregation(pipeline = {
            "{$match: ?0}",
            "{$sort: ?1}",
            "{$skip: ?2}",
            "{$limit: ?3}"
        })
        @Meta(allowDiskUse = true)
        public Page<Entity> findEntities(
            @Param("matchCriteria") Map<String, Pattern> filters,
            @Param("sortCriteria") Map<String, Integer> sort,
            @Param("skip") long skip,
            @Param("limit") long limit
        );
    }
    

    And then, in your implementation, you can dynamically create the $match criteria using the provided filters map:

    public class EntityRepositoryImpl<Entity, String> extends SimpleMongoRepository<Entity, String> implements EntityRepository<Entity, String> {
    
        private final MongoOperations mongoOperations;
    
        public EntityRepositoryImpl(MongoEntityInformation<Entity, String> metadata, MongoOperations mongoOperations) {
            super(metadata, mongoOperations);
            this.mongoOperations = mongoOperations;
        }
    
        @Override
        public Page<Entity> findEntities(Map<String, Pattern> filters, Map<String, Integer> sort, long skip, long limit) {
            Criteria matchCriteria = createMatchCriteria(filters);
    
            Aggregation aggregation = Aggregation.newAggregation(
                Aggregation.match(matchCriteria),
                Aggregation.sort(sort),
                Aggregation.skip(skip),
                Aggregation.limit(limit)
            );
    
            // Execute aggregation and return the result
            return mongoOperations.aggregate(aggregation, getEntityClass(), getEntityClass());
        }
    
        private Criteria createMatchCriteria(Map<String, Pattern> filters) {
            Criteria matchCriteria = new Criteria();
            for (Map.Entry<String, Pattern> entry : filters.entrySet()) {
                matchCriteria.and(entry.getKey()).regex(entry.getValue());
            }
            return matchCriteria;
        }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search