skip to Main Content

I have a mongo document that looks like the below JSON object. What I’m trying to do, using the mongoDb Query builder, is return all books from user 1 that are read: false.

For example:

var query = new Query();
query.addCriteria(Criteria.where("id").is(1));
query.fields().elemMatch("books", Criteria.where("read").is(false));

return users 1 and the first unread book but i’d like the full list of unread box.

Users:[
  {
    id: 1,
    name: 'John Doe',
    books: [
      {
        id: 1,
        title: 'The Hobbit',
        read: false
      },
      {
        id: 2,
        title: 'The Lord of the Rings',
        read: false
      },
      {
        id: 3,
        title: 'The Silmarillion',
        read: false
      }
    ]
  },
  {
    id: 2,
    name: 'Jane Doe',
    books: []
  }
}

2

Answers


  1. Chosen as BEST ANSWER

    Here is a solution using a MongoRepository<Users,Long> Interface and @Aggregation pipeline. Cleaner then the query builder IMO and supports searching and paging.

    @Aggregation(pipeline = {
            "{'$match':{'userId': ?0 }}", // filter by userid
            "{'$project':{'books':1}}", // only include books in the pipeline
            "{'$unwind': '$books'}", // 'unwind' the books array
            "{'$match':{'books.read': ?1, 'books.title': { '$regex' : ?2, '$options' : 'i'}}}", // filter by read and title
            "{'$group': {'_id': $_id, 'count': {$sum:  1}, 'books': {'$push': '$books'}}}", // group by full count (for pagination) and books
            "{'$project':{'_id': 0, 'count': 1, 'books': {$slice: ['$books', ?3, ?4]}}}", // define what to return (count, subset of books)
    })
    List<BookList> findByUserIdAndRead(Long userId, boolean read, String filter, long skip, int size);
    

    BookList class

    public class BookList {
        private Integer count;
        private List<Book> books;
    }
    

  2. You can use filter with project stage in an aggregation pipeline.

    db.collection.aggregate([
      {
        $match: {
          "id": 1
        }
      },
      {
        $project: {
          "books": {
            $filter: {
              input: "$books",
              as: "b",
              cond: {
                $eq: ["$$b.read",false]
              }
            }
          }
        }
      }
    ])
    

    Mongo Playground

    The query translates to this project stage of the pipeline.

    AggregationOperation matchStage = Aggregation
                    .match(Criteria.where("id").is(1));
    
    AggregationOperation projectStage = Aggregation.project()
                    .and(ArrayOperators.Filter
                            .filter("books")
                            .as("b")
                            .by(Eq.valueOf("read").equalToValue(false)))
                    .as("books");
    
    List<Users> users = mongoTemplate.aggregate(
        Aggregation.newAggregation(matchStage, projectStage), 
        Users.class,   //collection class
        Users.class //return type class
    ).getMappedResults();
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search