Spring Boot MongoDb условно возвращает элементы из списка документов

У меня есть документ монго, который выглядит как объект JSON ниже. То, что я пытаюсь сделать, используя построитель запросов mongoDb, возвращает все книги от пользователя 1, которые прочитаны: false.

Например:

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

вернуть пользователей 1 и первую непрочитанную книгу, но мне нужен полный список непрочитанных ящиков.

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: []
  }
}
Использование JavaScript и MongoDB
Использование JavaScript и MongoDB
Сегодня я собираюсь вкратце рассказать о прототипах в JavaScript, а также представить и объяснить вам работу с базой данных MongoDB.
0
0
122
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Вы можете использовать фильтр со стадией проекта в конвейере агрегации.

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

Детская площадка Монго

Запрос преобразуется в этот этап проекта конвейера.

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

как бы вы ограничились только пользователем 1?

wipallen 05.12.2022 13:53

обновлено, чтобы добавить этап матча.

Noel 05.12.2022 14:14

будет ли «класс возвращаемого типа» Book.class? К сожалению, не возвращает никаких результатов.

wipallen 05.12.2022 16:28

Users Ваш коллекционный класс? Поскольку форма документа не меняется, вы можете использовать Users.class для обоих.

Noel 06.12.2022 04:50

это работает. хотя мне пришлось изменить Eq.valueOf("read").equalToValue(false) на Eq.valueOf("b.read").equalToValue(false)

wipallen 06.12.2022 16:18

Вот решение, использующее интерфейс MongoRepository<Users,Long> и конвейер @Aggregation. Более чистый, чем построитель запросов IMO, и поддерживает поиск и пейджинг.

@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

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

Другие вопросы по теме