Когда я начинал работать с MongoDB и Java, мне часто приходилось сталкиваться с трудностями при выборе правильной операции обновления из различных методов, предоставляемых MongoDB. Даже во время код-ревью я получал комментарии от рецензента, где мне предлагали воспользоваться, к примеру, findAndModify() вместо updateMulti().

Здесь мы обсудим различные типы операций обновления в MongoDB и то, чем они отличаются друг от друга. Я буду использовать Java 8 с фреймворком SpringBoot и реализовывать пользовательские сценарии, которые можно найти на Github.

Мы будем оценивать все операции обновления на основе пяти параметров.

  1. Критерии поиска (Search Criteria).
  2. Значение обновления (весь документ или определение обновления).
  3. Возвращаемое значение (весь документ или статистика результатов обновления).
  4. Поведение по умолчанию, если соответствующий(е) документ(ы) не найден(ы). (вставлять/не вставлять/гибкий ответ).
  5. Дельта-обновление.

Для взаимодействия с базой данных Mongo воспользуемся простым объектом документа “Город” (City).

Теперь углубимся в тему.


save()

Метод save() не принимает никаких параметров и критериев поиска для документа, который необходимо обновить. По умолчанию он пытается найти документ через его _id, если он предоставлен вместе с объектом документа.

Если документ найден, метод save() обновляет документ или создает новый.

Метод save() возвращает обновленный или вновь созданный объект документа.

Недостаток этого метода заключается в том, что всегда необходимо предоставлять весь объект документа в качестве входного параметра, даже если требуется обновить только одно или два поля из двадцати. Это означает, что он не допускает дельта-обновления.

Метод save в MongoTemplate. MongoRepository также использует метод save похожим образом.

saveAll()

Метод saveAll() в точности похож на метод save(), за исключением двух аспектов.

Первый: вместо одного объекта документа он принимает в качестве входного параметра коллекцию объектов, к примеру List<City>. Второй: возвращает он также коллекцию обновленных/вставленных объектов документа, таких как List<City>.

upsert()

Метод upsert() похож на метод save() в том, что он обновляет существующий документ, если он найден, либо создает новый в зависимости от предоставленных данных. Но между этими методами есть и пара различий.

В отличие от save(), upsert() принимает критерии других полей, а не только _id, для нахождения документа, который подлежит обновлению.

upsert() возвращает объект подтверждения, содержащий сведения об операции обновления, такие как upsertedId, matchedCount и modifiedCount.

upsert() принимает не весь объект документа, а только определение обновления (UpdateDefinition), содержащее сведения обо всех полях, которые должны быть обновлены.

Эта функциональность имеет свои преимущества и недостатки.

Преимущество состоит в том, что можно выполнять дельта-обновление. Например, если объект содержит двадцать полей, то можно явно обновить любые два-три поля или n полей. Чтобы сохранить эти изменения, не нужно снова предоставлять весь объект.

Недостаток же заключается в том, что если документ по заданным критериям не будет найдет, то upsert() создаст новый объект, где будут только поля, указанные в UpdateDefinition. Это может привести к добавлению в базу неполных и поврежденных данных.

Метод upsert в MongoTemplate

findAndModify()

Метод findAndModify() соединяет лучшее из методов upsert() и save().

Как и upsert(), findAndModify() принимает критерии от других полей, а не только _id, чтобы найти документ, подлежащий обновлению.

Здесь также доступно дельта-обновление, как и в upsert().

Функция findAndModify() принимает не весь объект документа, а только определение обновления (UpdateDefinition), содержащее сведения о том, какие поля должны быть обновлены

Как и save(), findAndModify() возвращает весь объект документа. Кроме того, мы получаем возможность указать, хотим ли мы получить в качестве возвращаемого значения старый объект документа до обновления или новый объект документа после обновления.

Мы можем выбрать, хотим ли вставить новый документ, если не найден тот, что соответствует поисковому запросу, предоставив опцию upsert(true) методу findAndModify().

findAndReplace()

findAndReplace() позволяет найти документ, используя запрос по любому полю. Как только документ найден, происходит замена этого документа с использованием нового, который мы предоставили в запросе.

Мы можем выбрать, хотим ли создать новый документ, если не найден тот, что соответствует поисковому запросу, предоставив опцию upsert() методу findAndReplace().

Мы также можем выбрать, будет ли обновленный документ возвращаемым значением, предоставив опцию returnNew() методу findAndReplace().

Дельта-обновление в этом случае невозможно, так как для замены существующего документа нужно предоставить весь используемый документ целиком.

updateFirst()

updateFirst() принимает критерии других полей, а не только _id, чтобы найти документ, подлежащий обновлению.

Он также позволяет выполнять дельта-обновления, как и upsert().

Как и upsert(), updateFirst() возвращает объект подтверждения, содержащий сведения об операции обновления, такие как upsertedId, matchedCount и modifiedCount.

Но, если соответствующий документ не найден, новый документ в базе данных на основе полей, предоставленных в UpdateDefinition, не будет создан. 

Единственное различие между updateFirst() и другими операциями дельта-обновления заключается в том, что он обновит только первый документ из всех, что соответствуют критериям обновления.

updateMulti()

updateMulti() в точности повторяет updateFirst(), за исключением одной детали. В отличие от updateFirst(), этот метод обновит все документы, соответствующие критериям обновления.

BulkOps вместе с UpdateOne()

Метод массовых операций c применением updateOne() принимает один или несколько входных параметров, которые используются с отдельными запросами. Также необходимо предоставить обновленные определения, соответствующие каждому поисковому запросу. Это означает, что нужно предоставить “пары” поисковых запросов и обновленных определений, чтобы указать, какие данные следует обновлять, когда удовлетворяется конкретный поисковый запрос.

Это также подразумевает, что все операции массового обновления являются дельта-обновлениями и, в отличие от методов save() и findAndReplace(), не принимают целые документы в качестве входных данных.

“Первый” документ, который удовлетворяет каждому запросу в отдельности, будет обновлен.

Если соответствующий документ не найден, он не создаст новый в базе данных на основе полей, предоставленных в UpdateDefinition.

Обновления для всех запросов являются взаимоисключающими, т.е. документ не обязательно должен удовлетворять нескольким запросам.

Как и метод updateFirst(), BulkOps, использующий updateOne(), возвращает объект подтверждения, содержащий сведения об операции обновления, такие как upsertedId, matchedCount и modifiedCount.

BulkOps с UpdateMulti()

Массовые операции с использованием updateMulti() в точности аналогичны массовому копированию с использованием updateOne(), за исключением одной детали. В отличие от массовых операций, использующих updateOne(), этот запрос обновит все документы, которые соответствуют любому из запросов из критериев обновления.


Сравнительная таблица по операциям

Теперь вам будет легче выбрать подходящий метод обновления из вышеперечисленных, взвесив все их плюсы и минусы.

Полный код всех примеров можно найти здесь.

Читайте также:

Читайте нас в TelegramVK и Дзен


Перевод статьи Dhaval Simaria: Types of Update operations in MongoDB using Spring Boot

Предыдущая статьяКак с помощью Sentry реализовать захват исключений фронтенда
Следующая статьяКак работает обратное распространение в нейронных сетях