I've tried multiple methods to get Mongo's filter tool to work on Spring Aggregation function. I'm using version 3.1.0 of Spring Boot starter along with the mongo data starter package that comes with it. Documentation says it now supports filter (and addField), but I've had no luck.

My model is very simple. An embedded List of Mail in Mailbox. I want to find a Mailbox by its id first and then search its embedded list of Mail to only return a single Mail from it. I'm assuming an Aggregation with Filter is the most efficient way to do this as opposed to Unwind (which operates on all the Mail in the embedded List). The List<Mail> will become huge.

public class Mail {

    @MongoId
    private ObjectId id;

    @Indexed(unique=true)
    @Field(targetType = FieldType.BINARY)
    private UUID permacode;

    @Transient
    private List<Contest> contests = new ArrayList<Contest>();
//More properties, getters, setters, constructors, etc. ...

Mail is embedded as a List in Mailbox.

@Document
public class MailBox {

    @MongoId
    private ObjectId id;

    //Big list.
    private List<Mail> mails = new ArrayList<Mail>();

//Getters, setters, constructors, etc...

And here is the Aggregation (the last one I tried to use).

@Transactional
    public Mail findMailByIdAndMails_Id(ObjectId mailBoxId, ObjectId mailId) throws DataNotFoundException {

     MatchOperation matchStage = Aggregation.match(new Criteria("id").is(mailBoxId));
     AggregationOperation filterStage = Aggregation
          .addFields()
          .addField("mailsFiltered")
          .withValue(
               ArrayOperators
                    .arrayOf(ObjectOperators
                         .valueOf("mails")
                         .toArray()
                    )
                    .filter()
                    .as("mail")
                    .by(ComparisonOperators
                         .valueOf("mail.id")
                         .equalToValue(mailId)
                    )
               )
               .build();

     TypedAggregation<MailBox> agg = Aggregation.newAggregation(MailBox.class, matchStage, filterStage);
     //template is autowired MongoTemplate
     AggregationResults<MailBox> output = template.aggregate(agg, MailBox.class, MailBox.class);
     List<MailBox> mailBoxes = output.getMappedResults();
     if (mailBoxes.size() == 1) {
        if (mailBoxes.get(0).getMails().size() == 1) {
            return mailBoxes.get(0).getMails().get(0);
        }
    }
    throw new DataNotFoundException("Could not find the mail in DB.");
 }

The matchStage seems to work, but the filterStage will not. At best, it will return the single MailBox with all the mails in List<Mail> mails, but ignore the filter and new field. (Note: I plan to add a projectStage to select only the new mailsFiltered field, but haven't done that yet). I've also tried using filter with a Aggregation.project() instead of Aggregation.addFields() operation, but that won't work either. Older issue responses recommend using unwind. If Unwind is still the only way to do this, please let me know how to use the Unwind in the most efficient manner (preferrable using java and TypedAggregation as documented by Spring). Otherwise, I'm assuming it is just as efficient to return the entire List of Mail and search them manually external to Mongo.

0

There are 0 best solutions below