Как получить идентификатор объекта документа после вставки в реактив-монго?

Я видел, что этот вопрос, кажется, задавался ранее (почти 3 года назад), но с тех пор в библиотеке реактивного монго может быть много изменений.

Я использую плагин play с версией 2.4, но в реактивном реактиве mongo.api.commands.WriteResult, похоже, нет API для получения идентификатора объекта документа.

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

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

2 ответа

Решение

Создание ObjectId на стороне клиента все в порядке. Это что casbah (блокирующий драйвер MongoDB для MongoDB) работает, если вы копаетесь в его коде.

Если вы посмотрите на ObjectId поля, то вы найдете a 3-byte machine identifier среди других. Это рассчитано сложно (с участием InetAddress в стандартном драйвере Java). Это гарантирует, что не может быть столкновения между 2 ObjectIds, генерируемые одновременно на разных машинах.

Есть по крайней мере два правильных способа решить эту проблему. На самом деле второй - скорее хак, но он работает правильно. Я создал суть с полным примером (проверено).

генерировать BSONObjectId на клиенте

BSONObjectId имеет метод, называемый generate который вы можете использовать, чтобы легко создать уникальный идентификатор. Результирующий идентификатор имеет ту же структуру, что и идентификаторы, сгенерированные самим сервером MongoDB:

+------------------------+------------------------+------------------------+------------------------+
+ timestamp (in seconds) +   machine identifier   +    thread identifier   +        increment       +
+        (4 bytes)       +        (3 bytes)       +        (2 bytes)       +        (3 bytes)       +
+------------------------+------------------------+------------------------+------------------------+

(взято из Javadoc ReactiveMongo для BSONObjectId.generate)

Пример кода:

val id = BSONObjectId.generate()
collection.insert(BSONDocument("_id" -> id, "foo" -> "bar"))

Полученный идентификатор не будет точно таким же, как сгенерированный сервером. В частности, machine identifier а также thread identifier будет совершенно другим, скорее всего. Однако в большинстве (всех?) Случаев это не имеет значения, поскольку риск столкновения незначителен, поэтому, на мой взгляд, это лучший подход.

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

использование BSONCollection.findAndUpdate

Если вам действительно необходимо сгенерировать идентификаторы на стороне сервера по какой-то непонятной причине, это решение должно сделать это, избегая условий гонки или каких-либо дополнительных запросов, поэтому он является довольно оптимальным, хотя и несколько хакерским. Хитрость в том, чтобы использовать MongoDB findAndModify, Таким образом, вы получите FindAndModifyResult вместо WriteResult, который дает вам доступ к вставленному документу. Пример:

val resultFuture = collection.findAndUpdate(
    selector       = BSONDocument("foo" -> -1),    // Assuming that there's no document with "foo" equal to -1, otherwise THIS CODE MAY EXPLODE
    update         = BSONDocument("foo" -> "bar"),
    fetchNewObject = true,
    upsert         = true)
val idFuture: Future[BSONObjectId] = resultFuture.map { result =>
    val doc = result.value.get // WARNING: I'm using get for simplicity, but you shouldn't: it may throw an exception
    doc.getAs[BSONObjectId]("_id"))
}

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

Бонус: используйте отдельную коллекцию для дополнительных идентификаторов

Вы также можете создать дополнительную коллекцию для хранения самого последнего идентификатора, как описано в документации MongoDB. Это должно быть безопасно, пока вы используете findAndModify одновременно получить новый идентификатор и увеличить значение. Недостатки? Одна дополнительная коллекция и один дополнительный запрос, но в некоторых случаях вам все равно нужно автоматически увеличивать уникальные идентификаторы, поэтому стоит подумать.

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