I have documents with dynamic fields and I would need to find a count of matching records for a given complex query criteria
Example Entity
@Document(collection = "UserAttributes") public class UserAttributesEntity { @Id @Getter private String id; @NotNull @Size(min = 1) @Getter @Setter private String userId; @NotNull @Getter @Setter private Map<String, Object> attributes = new HashMap<>(); }
Example Data:
{ "_id" : ObjectId("6164542362affb14f3f2fef6"), "userId" : "89ee6942-289a-48c9-b0bb-210ea7c06a88", "attributes" : { "age" : 61, "name" : "Name1" } }, { "_id" : ObjectId("6164548045cc4456792d5325"), "userId" : "538abb29-c09d-422e-97c1-df702dfb5930", "attributes" : { "age" : 40, "name" : "Name2", "location" : "IN" } }
Expected Query Criteria:
"((userAttributes.name == 'Name1' && userAttributes.age > 40) OR (userAttributes.location == 'IN'))
Building such complex Criteria using $match would be too much of implementation, so I was trying to use SPEL evolution through $project like below:
private Mono<Long> aggregate() { final Aggregation aggregation = Aggregation .newAggregation( Aggregation.project("userAttributes.playerLevel", "userAttributes.name") .andExpression("((userAttributes.name == 'Name1' && userAttributes.age > 40) OR (userAttributes.location == 'IN'))") .as("result"), Aggregation.match(Criteria.where("result").is(true)), Aggregation.group().count().as("count")); return mongoTemplate.aggregate(aggregation, UserAttributesEntity.class, Map.class) .map(result -> Long.valueOf(result.get("count").toString())) .next(); }
However, the above logic failing due to an exception:
org.springframework.data.mapping.MappingException: Couldn't find PersistentEntity for type java.lang.Object! at org.springframework.data.mapping.context.MappingContext.getRequiredPersistentEntity(MappingContext.java:119) at org.springframework.data.mapping.context.PersistentPropertyPathFactory.getPair(PersistentPropertyPathFactory.java:226) at org.springframework.data.mapping.context.PersistentPropertyPathFactory.createPersistentPropertyPath(PersistentPropertyPathFactory.java:199) at org.springframework.data.mapping.context.PersistentPropertyPathFactory.lambda$getPersistentPropertyPath$1(PersistentPropertyPathFactory.java:172) at java.base/java.util.concurrent.ConcurrentMap.computeIfAbsent(ConcurrentMap.java:330) at org.springframework.data.mapping.context.PersistentPropertyPathFactory.getPersistentPropertyPath(PersistentPropertyPathFactory.java:171) at org.springframework.data.mapping.context.PersistentPropertyPathFactory.from(PersistentPropertyPathFactory.java:71) at org.springframework.data.mapping.context.AbstractMappingContext.getPersistentPropertyPath(AbstractMappingContext.java:295) at org.springframework.data.mongodb.core.aggregation.TypeBasedAggregationOperationContext.getReferenceFor(TypeBasedAggregationOperationContext.java:163) at org.springframework.data.mongodb.core.aggregation.TypeBasedAggregationOperationContext.getReference(TypeBasedAggregationOperationContext.java:107) at org.springframework.data.mongodb.core.aggregation.AggregationExpressionTransformer$AggregationExpressionTransformationContext.getFieldReference(AggregationExpressionTransformer.java:82) at org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer$CompoundExpressionNodeConversion.convert(SpelExpressionTransformer.java:541) at org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.transform(SpelExpressionTransformer.java:113) at org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.transform(SpelExpressionTransformer.java:58) at org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer$ExpressionNodeConversion.transform(SpelExpressionTransformer.java:215) at org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer$ExpressionNodeConversion.transform(SpelExpressionTransformer.java:205) at org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer$OperatorNodeConversion.convert(SpelExpressionTransformer.java:257) at org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.transform(SpelExpressionTransformer.java:113) at org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.transform(SpelExpressionTransformer.java:58) at org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer$ExpressionNodeConversion.transform(SpelExpressionTransformer.java:215) at org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer$ExpressionNodeConversion.transform(SpelExpressionTransformer.java:205) at org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer$OperatorNodeConversion.convert(SpelExpressionTransformer.java:251) at org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.transform(SpelExpressionTransformer.java:113) at org.springframework.data.mongodb.core.aggregation.SpelExpressionTransformer.transform(SpelExpressionTransformer.java:105) at org.springframework.data.mongodb.core.aggregation.ProjectionOperation$ExpressionProjectionOperationBuilder$ExpressionProjection.toMongoExpression(ProjectionOperation.java:438) at org.springframework.data.mongodb.core.aggregation.ProjectionOperation$ExpressionProjectionOperationBuilder$ExpressionProjection.toDocument(ProjectionOperation.java:433) at org.springframework.data.mongodb.core.aggregation.ProjectionOperation.toDocument(ProjectionOperation.java:261) at org.springframework.data.mongodb.core.aggregation.AggregationOperation.toPipelineStages(AggregationOperation.java:55) at org.springframework.data.mongodb.core.aggregation.AggregationOperationRenderer.toDocument(AggregationOperationRenderer.java:56) at org.springframework.data.mongodb.core.aggregation.AggregationPipeline.toDocuments(AggregationPipeline.java:81) at org.springframework.data.mongodb.core.aggregation.Aggregation.toPipeline(Aggregation.java:705) at org.springframework.data.mongodb.core.AggregationUtil.createPipeline(AggregationUtil.java:105) at org.springframework.data.mongodb.core.ReactiveMongoTemplate.aggregate(ReactiveMongoTemplate.java:1001) at org.springframework.data.mongodb.core.ReactiveMongoTemplate.aggregate(ReactiveMongoTemplate.java:970)
I tried digging in further, it seems to fail while its trying to map the fields used within the project to a proper entity object and in this case the projection fields here are actually are of Map object and its unable to identify the matching Entity (of-course the Map is within the UserAttributesEntity) – Is there a better way to solve my problem other than the above approach?
Advertisement
Answer
Using the raw aggregation resolved the issue:
return mongoTemplate.aggregate(aggregation, mongoTemplate.getCollectionName(UserAttributesEntity.class), Map.class) .map(result -> Long.valueOf(result.get("count").toString())) .next()