Home > Enterprise >  Spring Boot MongoDb return items from Document list conditionally
Spring Boot MongoDb return items from Document list conditionally

Time:12-07

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: []
  }
}

CodePudding user response:

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();

CodePudding user response:

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;
}
  • Related