如何在 reactivemongo 中插入后获取文档对象 ID?

How to get the document object id after insert in reactivemongo?

我看到这个问题似乎很早就有人问过(差不多 3 年前),但从那时起,反应式 mongo 库可能会有很多变化。

我正在使用 2.4 版本的 play 插件,但是 reactivemongo.api.commands.WriteResult 似乎没有任何 API 来获取文档对象 ID。

现在我可以开始自己设置对象 ID,但我不认为这是一个令人信服和正确的想法,因为我创建 ID 的机器上的一些唯一值可能与另一台机器不相同并保持事情很简单,我想让这个由 mongo db.

处理

所以是的,如果有某种方法我可以获得插入文档的 ID 会很棒,否则我必须回退到自己设置 ID 的方式,这是我试图避免的事情。

在客户端创建 ObjectId 就可以了。如果你深入研究它的代码,这就是 casbah(MongoDB 的阻塞 MongoDB 驱动程序)正在做的事情。

如果您查看 ObjectId 个字段,您会发现 a 3-byte machine identifier 个字段。它的计算很棘手(涉及标准 Java 驱动程序中的 InetAddress)。它保证在不同机器上同时生成的2 ObjectIds之间不会发生冲突。

至少有两种正确的方法可以解决这个问题。实际上,第二个更像是一个 hack,但它工作正常。我创建了一个 gist with a complete example (tested).

在客户端生成BSONObjectId

BSONObjectId 有一个名为 generate 的方法,您可以使用它轻松生成唯一 ID。生成的 ID 与 MongoDB 服务器本身生成的 ID 具有相同的结构:

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

(取自 BSONObjectId.generate 的 ReactiveMongo 的 javadoc)

示例代码:

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

生成的 ID 不会与服务器生成的完全相同。具体来说,machine identifierthread identifier 很可能是完全不同的。然而,在大多数(所有?)情况下,这并不重要,因为碰撞的风险可以忽略不计,所以我认为这是最好的方法。

您还应该检查 insert 返回的值。如果失败,您可以生成一个新 ID 并再次尝试插入。这样你的代码应该是防弹的。请记住限制重试次数,并可能在每次尝试之间添加一些随机延迟。

使用BSONCollection.findAndUpdate

如果您出于某些不明确的原因确实需要在服务器端生成 ID,此解决方案应该可以做到这一点,从而避免竞争条件或任何其他查询,因此它是最佳选择,尽管有些 hacky。诀窍是使用 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.

中的值

奖励:为增量 ID 使用单独的集合

您还可以创建一个额外的集合来保留最新的 ID,如 MongoDB's documentation 中所述。只要您使用 findAndModify 同时获取新 ID 并增加值,这应该是安全的。缺点?一个额外的集合和一个额外的查询,但在某些情况下您仍然需要自动递增的唯一 ID,因此可能值得考虑。