Предположим, у меня есть коллекция документов, каждый из которых имеет следующий набор свойств:
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
не подтверждено?
Немного связано: