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

时间:2016-03-23 13:29:56

标签: mongodb scala reactivemongo play-reactivemongo

我已经看到这个问题似乎早些时候(差不多3年前)被提出过,但从那时起,反应性mongo库可能会有很多变化。

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

现在我可以自己开始设置对象id了,但是我觉得它不是一个令人信服的正确想法,因为我创建id的机器上的一些唯一值可能与其他机器不一样并且保持简单我想让这个由mongo db处理。

所以是的,如果有某种方式我可以获得插入文档的ID将是伟大的,否则我必须回退到我自己设置id的方式,我试图避免。

2 个答案:

答案 0 :(得分:4)

至少有两种方法可以解决这个问题。实际上,第二个更像是一个黑客,但它运作正常。我创建了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)       +
+------------------------+------------------------+------------------------+------------------------+

(取自ReactiveMongo的BSONObjectId.generate的javadoc)

示例代码:

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

生成的ID与服务器生成的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使用单独的集合

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

答案 1 :(得分:1)

在客户端创建ObjectId很好。如果你深入研究它的代码,casbah(MongoDB的阻塞MongoDB驱动程序)正在做什么。

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