我可以为MongoDB文档使用字符串作为ID类型吗?

32

我正在使用java/morphia来处理mongodb,但默认的ObjectId在Java层面上不太方便使用。我希望将其转换为字符串类型,同时保留使用ObjectId进行键生成的过程,比如说_id = new ObjectId.toString()

我想知道采用这种方式是否会产生任何副作用?例如,它是否会影响数据库性能或导致键冲突?它会影响分片环境吗...


你能解释一下为什么 ObjectId 不方便吗?你可以轻松地通过字符串重新创建一个,例如 id = new ObjectId(str) - Nic Cottrell
1
String是每个Java程序中使用的类型,而ObjectId则不是。我不想向使用我的库的其他组件引入新类型。虽然可能需要导入morphia、mongodb和bson库,但如果使用我的库的人对包括ObjectId在内的mongodb类型透明,则仍然更好。 - Gelin Luo
嗯...但是如果你的库将数据存储在Mongo中,那么mongo.jar不管怎样都需要在类路径中吧?此外,创建和垃圾回收大量字符串会产生开销。 - Nic Cottrell
如果您想为ID获取一个新的字符串值,请使用(new ObjectId).valueOf()而不是(new ObjectId).toString()(在MongoDB ver 2.2中更改)。 - Mika Vatanen
5个回答

34

_id字段中,您可以使用任何类型的值(数组除外)。如果您选择不使用ObjectId,则必须以某种方式确保值的唯一性(将ObjectId强制转换为字符串即可)。如果尝试插入重复键,则会出现错误,您必须处理它。

我不确定在尝试向不同分片插入具有相同_id的两个文档时会产生什么影响。我怀疑它会让你插入,但这以后会困扰你。(我需要测试一下)。

话虽如此,_id = (new ObjectId).toString()应该没有问题。


1
你的第二句和第三句话相互矛盾,第三句是正确的。不要怀疑答案。此外,“_id”可以保存“数组”类型的值。 - Dyin
它们到底是如何相互矛盾的? - Sergio Tulentsev
3
如果您想为ID获得一个新的字符串值,请使用(new ObjectId).valueOf()而不是(new ObjectId).toString()(在MongoDB 2.2版本中更改)。 - Mika Vatanen

7

我也曾经遇到过同样的问题,因为我无法将ObjectId转换为JSON格式。

后来我尝试了以下方法:

@Id
private String id;
public String getId() {
    return id();
}
public void setId(String id) {
    this.id = id;
}

一切都很顺利,直到我决定更新先前插入的文档。当我通过 ID 获取该对象并通过 JSON 将其发送到页面时,也通过 JSON post 接收到了相同的更新后的对象,然后使用 Datastore 的 save 函数,而不是更新之前的数据,它插入了一个新文档,而不是更新已经存在的文档。

更糟糕的是,新文档具有先前插入的文档相同的 ID,这是我认为不可能的事情。

无论如何,我将私有对象设置为 ObjectID,并将 get 设置为字符串,然后它按预期工作了。不确定是否适用于您的情况。

@Id
private ObjectId id;
public String getId() {
    return id.toString();
}
public void setId(String id) {
    this.id = new ObjectId(id);
}

6
是的,你可以使用字符串作为你的_id。
我建议只有当你在文档中有一些自然而然就是一个好的唯一键时才使用它。我在一个集合中使用了这个设计,其中有一个字符串地理标记,形式为“xxxxyyyy”;这个唯一的每个文档字段必须在文档中 存在,并且我必须在它上面建立索引...那么为什么不将其用作键呢?(这避免了一个额外的键值对,并避免了在集合上建立第二个索引,因为MongoDB自然地在“_id”上建立索引。鉴于集合的大小,这两个问题加起来节省了一些空间。)
但是,根据你问题的语气(“ObjectIDs不是很方便”),如果你想使用字符串的唯一原因是你不想被麻烦地弄清楚如何整洁地管理ObjectIDs...我建议你花时间去理解它们。我相信一旦你弄清了它们的问题,它们就不会有任何问题。
否则:你有哪些选择?你会在以后的每次使用MongoDB时都编制字符串ID吗?

7
谢谢你回到我的问题,这个问题已经快四年了。我认为自那时以来我对MongoDB有了更深入的理解。现在我认为把ObjectId存储为字符串真的是一个非常糟糕的主意,因为ObjectId实际上是三个整数,而它的字符串表示形式是24个字符,这在空间和时间方面都不太有效率。 - Gelin Luo

1
我想补充一点,如果自动生成的BSON ObjectID作为唯一标识符被传递给应用程序,不总是一个好主意,因为它有可能会被用户操纵。
ObjectIDs似乎是按顺序生成的,因此如果您未实现必要的授权机制,恶意用户可以简单地增加他所拥有的值,以访问他不应访问的资源。
更新:自版本3.4+以来,ObjectIDs不再按顺序生成。请参见 3.2文档最新文档 因此,使用UUID类型标识符将提供一层安全性-通过模糊性。当然,授权(此用户是否被允许访问所请求的资源)是必须的,但您应该注意前面提到的ObjectID功能。
为了兼顾两者,生成与您的ObjectID长度匹配的UUID,并使用它创建自己的ObjectID类型的_id。

3
如果您未能实施必要的授权,无论如何都注定会失败。晦涩不是安全,只会骗自己“我们很安全”。 - Piohen
因此,使用UUID类型的标识符将提供一层安全性。这实际上是每个文档的密码。 - GAllan
-1 是因为 ObjectID 并非按顺序生成,尽管乍一看可能是这样。它们包括时间戳、随机值和计数器。https://docs.mongodb.com/manual/reference/method/ObjectId/ - Dushyant Bangal
@DushyantBangal 这有点不礼貌,因为答案是在3.4版本发布之前给出的。该答案对于那个版本是正确的。更好的做法是强调变化并促进更新答案(特别是这个小细节不会改变答案的本质)。 - tonysepia
@TonySepia,它仍然具有“表示自Unix纪元以来的秒数的4字节值”,因此在您说“用户可以简单地递增他拥有的值”的意义上仍不是顺序的。如果我说话粗鲁了,我很抱歉,但通常人们甚至不会解释为什么要打-1,并给别人一个改正答案的机会。 - Dushyant Bangal
此外,这完全改变了答案的本质。使用ObjectId作为唯一标识符是完全可以的,而你说不行。答案甚至没有回答OP的问题 - 他们能否使用字符串,并且是否会有任何性能副作用。 - Dushyant Bangal

0

你也可以从Java中使用字符串作为ID。这里有一个示例方法以及相应的单元测试,在MongoDB集合中插入一个具有字符串ID的对象:

public Document insert(String json, String collectionName) {
    MongoCollection<Document> collection = database.getCollection(collectionName);
    BasicDBObject document = BasicDBObject.parse(json);
    Document doc = new Document(document);
    collection.insertOne(doc);
    return doc;
}

@Test
void whenInsert_ShouldInsertOne() {
    final String uuid = UUID.randomUUID().toString();
    final String collection = "test_collection";
    final Document doc = app.insert(String.format("{\"_id\":\"%s\", \"name\": \"test\"}", 
        uuid), collection);
    assertThat(doc).isNotNull();
    final String json = app.getById(uuid, collection);
    assertThat(json).contains(String.format("\"_id\": \"%s\"", uuid));
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接