至少有两种适当的方法可以解决这个问题。实际上,第二个更像是一种黑客攻击,但它可以正常工作。我创建了一个带有完整示例(经过测试)的要点。
BSONObjectId
在客户端生成
BSONObjectId
有一个名为的方法generate
,您可以使用该方法轻松生成唯一 ID。生成的 ID 与 MongoDB 服务器自身生成的 ID 具有相同的结构:
+------------------------+------------------------+------------------------+------------------------+
+ timestamp (in seconds) + machine identifier + thread identifier + increment +
+ (4 bytes) + (3 bytes) + (2 bytes) + (3 bytes) +
+------------------------+------------------------+------------------------+------------------------+
(取自 ReactiveMongo 的 javadoc for BSONObjectId.generate
)
示例代码:
val id = BSONObjectId.generate()
collection.insert(BSONDocument("_id" -> id, "foo" -> "bar"))
生成的 ID 不会与服务器生成的完全相同。具体来说,machine identifier
很thread 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 的文档中所述。只要您findAndModify
用于同时获取新 ID 并增加值,这应该是安全的。缺点?一个额外的集合和一个额外的查询,但在某些情况下,无论如何您都需要自动递增的唯一 ID,因此可能值得考虑。