I am using MongoDB in a reactive Spring Boot application (using Webflux) and I want to do an aggregation on one of our collections but it is just returning an empty list. The code below shows the aggregation and the equivalent query in the command line.
Running the aggregation in the command line works fine and after testing the code it seems to only be the .in(users)
part that is not working. So if I just use Criteria.where("_id").is("de28b2fa-4c9b-49fa-b29f-d8ad9d270c36")
then a User will be returned.
public Flux<User> findAllInIdList(List<UUID> users, long skip, long limit, Sort.Direction direction) {
List<AggregationOperation> aggs = new ArrayList<>();
aggs.add(match(Criteria.where("_id").in(users)));
aggs.add(sort(direction, Fields.CREATED_ON.getValue()));
aggs.add(Aggregation.skip(skip));
aggs.add(Aggregation.limit(limit));
TypedAggregation<User> aggregation = Aggregation.newAggregation(User.class, aggs);
return mongoTemplate.aggregate(aggregation, User.class);
}
db.User.aggregate([
{"$match" : {"_id" : {"$in" : ["de28b2fa-4c9b-49fa-b29f-d8ad9d270c36", "9f80bd61-be4d-4b3f-b78f-6172d88165b8", "f33e4769-a9d4-41d2-9f57-ac94afe932d7", "357397d8-ef12-42db-92cd-2cf5c31405ed", "5fc8acb6-c73e-4e8e-91b6-1347317da78b"]}}},
{"$sort" : {"record.createdOn" : 1}},
{"$skip" : 0},
{"$limit" : 50}
])
I also have other aggregations in the same class that work fine :
public Flux<User> findAll(long skip, long limit, Sort.Direction direction) {
List<AggregationOperation> aggs = new ArrayList<>();
aggs.add(sort(direction, Fields.CREATED_ON.getValue()));
aggs.add(Aggregation.skip(skip));
aggs.add(Aggregation.limit(limit));
TypedAggregation<User> aggregation = Aggregation.newAggregation(User.class, aggs);
return mongoTemplate.aggregate(aggregation, User.class);
}
EDIT:
As prasad pointed out in the comments the id in the DB are String types so I've also tried mapping the UUIDs in the code to a string and it still returns nothing:
public Flux<User> findAllWithCompany(List<UUID> users, long skip, long limit, Sort.Direction direction) {
List<AggregationOperation> aggs = new ArrayList<>();
List<String> idsAsString = users.stream().map(UUID::toString).collect(Collectors.toList());
aggs.add(match(Criteria.where("_id").in(idsAsString)));
aggs.add(sort(direction, Fields.CREATED_ON.getValue()));
aggs.add(Aggregation.skip(skip));
aggs.add(Aggregation.limit(limit));
TypedAggregation<User> aggregation = Aggregation.newAggregation(User.class, aggs);
return mongoTemplate.aggregate(aggregation, User.class);
}
EDIT
Adding additional information:
User entity:
@Value
@Jacksonized
@Document(collection = "Users")
@Builder(builderClassName = "Builder", toBuilder = true)
@CompoundIndexes({
@CompoundIndex(def = "{'id' : 1, 'record': 1}"),
})
public class User implements UserDetails {
@Serial
private static final long serialVersionUID = 1L;
@MongoId(targetType = FieldType.STRING)
UUID id;
@NonNull Person person;
Notification notification; // Not stored in DB
@NonNull Security security;
Record record;
@Override
public boolean isAccountNonExpired() {
return isEnabled();
}
@Override
public boolean isAccountNonLocked() {
return isEnabled();
}
@Override
public boolean isCredentialsNonExpired() {
return isEnabled();
}
@Override
public boolean isEnabled() {
return Objects.equals(this.security.status.getCode(), Fields.ACTIVE.getValue());
}
@Override
public Collection<GrantedAuthority> getAuthorities() {
return null;
}
@Override
public String getPassword() {
return security.hashPassword;
}
@Override
public String getUsername() {
return this.person.email;
}
@Value
@Document
@Jacksonized
@lombok.Builder(builderClassName = "Builder", toBuilder = true)
public static class Person {
@NonNull String firstName;
@NonNull String lastName;
String givenName;
@Indexed(unique = true)
@NonNull String email;
Set<String> phones;
String pictureUrl;
}
@Value
@Document
@Jacksonized
@lombok.Builder(builderClassName = "Builder", toBuilder = true)
public static class Security {
@NonNull String hashPassword;
@NonNull StatusRef status;
Instant securityLoggedOn;
Instant securityLastLoggedOn;
}
@Value
@Document
@Jacksonized
@lombok.Builder(builderClassName = "Builder", toBuilder = true)
public static class Notification {
Set<UserAlerts.Alerts> alerts;
Set<String> subscribedAlertTypes;
}
}
Example record in the Database:
{
"_id": "6bee42cc-1a16-11ec-9621-0242ac130002",
"person": {
"firstName": "Test",
"lastName": "1",
"givenName": "Test user",
"email": "test@onepointltd.com",
"phones": [
"+91-1234566",
"+91-1234566"
],
"picture": "https://picsum.photos/nik.jpg"
},
"notification": {},
"security": {
"hashPassword": "cBrlgyL2GI2GINuLUUwgojITuIufFycpLG4490dhGtY=",
"securityLoggedOn": 2021-09-23T07:36:21.330+00:00,
"securityLastLoggedOn": 2021-09-23T07:36:21.330+00:00,
"status": {
"code": "active",
"changedBy": "416bad39-02c5-45a5-802f-4a33cd24a2ee",
"changedOn": 2021-09-23T07:36:21.330+00:00
}
},
"record": {
"createdOn": 2021-09-23T07:36:21.330+00:00,
"createdBy": "416bad39-02c5-45a5-802f-4a33cd24a2ee",
"updatedOn": 2021-09-23T07:36:21.330+00:00,
"updatedBy": "416bad39-02c5-45a5-802f-4a33cd24a2ee"
}
}
The following code works based on @prasad_'s comment linking the following post: Spring Mongo Aggregation Project Filter
I just adapted this code to my reactive/webflux solution.
public Flux<User> findAllWithCompany(List<UUID> users, long skip, long limit, Sort.Direction direction) {
List<String> ids = users.stream().map(UUID::toString).collect(Collectors.toList());
Aggregation agg = newAggregation(
match(Criteria.where("_id").in(ids )),
sort(direction, Fields.CREATED_ON.getValue()),
skip(skip),
limit(limit)
);
return mongoTemplate.aggregate(agg, "Users", User.class);
}