Оптимальный поток для обновления/вставки документа

Предположим, у меня есть коллекция документов, каждый из которых имеет следующий набор свойств:

title: string,
year: integer,
published: boolean,
deleted: boolean

Свойства { title, year } образуют уникальный ключ, и такой ключ существует для данной коллекции. т.е. пользователю не разрешено создавать документ с тем же заголовком в том же году.

deleted играет следующую роль: когда пользователь запрашивает удаление документа, он фактически не удаляется, а только помечается как удаленный. Это необходимо для сбора статистики по количеству созданных документов (на одного пользователя и/или за год). При запросе документов удаленные документы отфильтровываются.

У меня есть задача реализовать такой поток, а именно:

  • Когда документ вообще не существует, его следует создать на основе предоставленных свойств. Новый идентификатор документа должен быть возвращен пользователю. Если пользователь предоставил идентификатор документа, следует использовать этот идентификатор.
  • Если документ существует, но помечен как удаленный, его следует восстановить и применить предоставленные свойства. Идентификатор документа должен быть возвращен пользователю.
  • В противном случае должно быть сгенерировано исключение дублирования документа.

Например, если документ {_id: ObjectId("5e8f2525a68af8514fb05f03"), title: "Investigation", year: 2005, published: true, deleted: true} существует, то если пользователь запрашивает создание документа:

  • { title: "Investigation", year: 2005 } документ {_id: ObjectId("5e8f2525a68af8514fb05f03"), title: "Investigation", year: 2005, published: true} возвращен
  • { title: "Investigation", year: 2005, published: false } документ {_id: ObjectId("5e8f2525a68af8514fb05f03"), title: "Investigation", year: 2005, published: false} возвращается (свойства применяются соответственно)

Пока я пришел к следующему решению (с точки зрения Spring Mongo):

import org.bson.BsonValue;
import org.bson.Document;

import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;

import static org.springframework.data.mongodb.core.query.Criteria.where;

public void createDocument(UserDocument userDocument) {
    Document document = new Document();
    mongoTemplate.getConverter().write(userDocument, document);

    Set<String> fields = new HashSet<>();
    Collections.addAll(fields, "title", "year"); // unique index

    Query query = new Query();
    Update update = new Update();

    update.unset("deleted");

    document.entrySet().forEach(entry -> {
        if (fields.contains(entry.getKey())) {
            query.addCriteria(where(entry.getKey()).is(entry.getValue()));
        } else {
            update.setOnInsert(entry.getKey(), entry.getValue());
        }
    });

    UpdateResult result = mongoTemplate.upsert(query, update, "documents");
    BsonValue upsertedId = result.getUpsertedId();

    if (upsertedId == null) {
        if (result.getModifiedCount() == 0) {
            throw new DuplicateDocumentException();
        }
        if (userDocument.getId() == null) {
            userDocument.setId(mongoTemplate.findAndReplace(query, userDocument, "document").getId());
        }
    } else {
        userDocument.setId(upsertedId.asObjectId().getValue().toHexString());
    }
}

Интересно, является ли данное решение оптимальным? Например, он делает два обращения к базе данных (upsert и findAndReplace), что, вероятно, можно сделать в одном запросе? Будет ли решение работать, если UpdateResult не подтверждено?

Немного связано:


person dma_k    schedule 09.11.2020    source источник