【问题标题】:How to insert a document with mgo and get the value returned如何用mgo插入文档并获取返回值
【发布时间】:2018-07-25 10:24:12
【问题描述】:

为了记录,我正在学习围棋。我正在尝试使用 mgo 包,我想插入一个新文档并将这个新创建的文档返回给用户(我正在尝试编写一个基本的 API)。我写了以下代码:

编辑:这是模型的结构:

type Book struct {
  ISBN    string   `json:"isbn"`
  Title   string   `json:"title"`
  Authors []string `json:"authors"`
  Price   string   `json:"price"`
}

session := s.Copy()
defer session.Close()

var book Book
decoder := json.NewDecoder(r.Body)
err := decoder.Decode(&book)
if err != nil {
    ErrorWithJSON(w, "Incorrect body", http.StatusBadRequest)
    return
}

c := session.DB("store").C("books")

info, err := c.Upsert(nil, book)

if err != nil {
    ErrorWithJSON(w, "Database error", http.StatusInternalServerError)
log.Println("Failed insert book: ", err)
    return
}

respBody, err := json.MarshalIndent(info, "", "  ")
if err != nil {
    log.Fatal(err)
}

ResponseWithJSON(w, respBody, http.StatusOK)

请注意 Book 是我之前创建的结构。上面的代码确实有效,但它返回的是 upsert 结果,如下所示:

{
    "Updated": 1,
    "Removed": 0,
    "Matched": 1,
    "UpsertedId": null
}

这不是最近创建的对象。如何让最近创建的对象作为响应返回(请注意,理想情况下,我希望确认文档已成功插入。我已经看到了其他问题,其中预先生成了 ID,但对于我所看到的它没有确认文档是创建的吗?)

【问题讨论】:

  • 在插入对象后查询该对象。
  • 但是查询的ID在哪里?
  • 在您的对象中。这显然是根据您引用的输出对现有对象的更新,这意味着您已经有了 ID。
  • 不,我正在尝试插入第一个。根据我目前找到的文档,Insert 也不提供 ID,因此建议使用 Upsert 方法
  • upsert 结果意味着您正在更新现有文档。

标签: mongodb go mgo


【解决方案1】:

让我们先弄清楚概念。在 MongoDB 中,每个文档都必须有一个 _id 属性,该属性充当其在集合中的唯一文档标识符。要么您提供此 _id 的值,要么它由 MongoDB 自动分配。

因此,您的模型类型最好(或强烈推荐)包含_id 文档标识符的字段。由于我们在这里讨论的是书籍,因此书籍已经有一个称为 ISBN 的唯一标识符,您可以选择将其用作 _id 字段的值。

MongoDB 字段和 Go 结构字段之间的映射必须使用 bson 标记(而不是 json)指定。所以你应该提供bson标签值以及json标签。

所以把你的模型改成:

type Book struct {
  ISBN    string   `json:"isbn" bson:"_id"`
  Title   string   `json:"title" bson:"title"`
  Authors []string `json:"authors" bson:"authors"`
  Price   string   `json:"price" bson:"price"`
}

如果你想插入一个新文档(一本新书),你应该总是使用Collection.Insert()

新插入的文档的 ID 是什么?您设置为 Book.ISBN 字段的字段,因为我们声明它是带有 bson:"_id" 标签的文档 ID。

如果您不确定文档是否已经存在,您应该只使用Collection.Upsert(),但无论哪种方式,您都希望它是您手头的文档。 Collection.Upsert() 将尝试查找要更新的文档,如果找到,则会更新该文档。如果没有找到文档,则将执行插入操作。第一个参数是查找要更新的文档的选择器。由于您通过了nil,这意味着任何文件都可能符合条件,因此将“随机”选择一个。因此,如果您已经保存了书籍,则任何书籍都可能被选中并被覆盖。这肯定不是你想要的。

既然现在 ISBN 是 ID,您应该指定一个按 ISBN 过滤的选择器,如下所示:

info, err := c.Upsert(bson.M{"_id": book.ISBN}, book)

或者由于我们是按 ID 过滤的,所以使用更方便的Collection.UpsertId()

info, err := c.UpsertId(book.ISBN, book)

如果您想更新现有文档,您可以使用Collection.Update()。这类似于Collection.Upsert(),但不同的是如果没有文档匹配选择器,则不会执行插入。更新与 ID 匹配的文档也可以使用更方便的Collection.UpdateId()(类似于Collection.UpsertId())来完成。

对于其他自然没有唯一标识符的文档(例如具有 ISBN 的书籍),您可以使用生成的 ID。 mgo 库为此提供了bson.NewObjectId() 函数,它返回一个bson.ObjectId 类型的值。使用Collection.Insert() 保存新文档时,您可以使用bson.NewObjectId() 获取新的唯一ID,并将其分配给映射到MongoDB _id 属性的struct 字段。如果插入成功,您可以确定文档的 ID 是您在调用 Collection.Insert() 之前设置的。此 ID 生成旨在即使在分布式环境中也能正常工作,因此即使您的 2 个节点尝试同时生成 ID,它也会生成唯一 ID。

因此,例如,如果您在保存图书时没有 ISBN,那么您必须在 Book 类型中具有单独的指定 ID 字段,例如:

type Book struct {
  ID      bson.ObjectId `bson:"_id"`
  ISBN    string        `json:"isbn" bson:"isbn"`
  Title   string        `json:"title" bson:"title"`
  Authors []string      `json:"authors" bson:"authors"`
  Price   string        `json:"price" bson:"price"`
}

保存新书时:

var book Book
// Fill what you have about the book
book.ID = bson.NewObjectId()

c := session.DB("store").C("books")
err = c.Insert(book)
// check error
// If no error, you can refer to this document via book.ID

【讨论】:

  • 感谢您的详细回复。我仍在努力让它发挥作用。我正在尝试按照您的建议使用 Insert,但是创建的文档没有正确的 ID (ISBN),并且在创建文档后我仍然没有取回文档。您能否根据我提供的代码提供一个示例?
  • @WagnerMatosUK 如果您在保存图书时没有 ISBN,则必须使用生成的 ID 作为文档 ID。请参阅我答案的最后一部分。您在保存之前设置了 ID,如果插入成功,则 ID 将与您在调用 Insert() 之前设置的完全相同。
  • @WagnerMatosUK 添加了如果 ISBN 不总是已知的情况下如何处理的示例。
  • 我在尝试使用book.ID = bson.NewObjectId() 时收到cannot use bson.NewObjectId() (type bson.ObjectId) as type string in assignment。知道为什么吗?
  • @WagnerMatosUK 因为如果您查看我的示例,Book.ID 的类型为 bson.ObjectId 而不是 string。修复你的代码。
【解决方案2】:

为此我头疼了一阵子。

info, err := c.Upsert(nil, book)

upsert 的 nil 选择器将匹配所有内容,因此如果您的集合为空,则选择器将不匹配并且一切正常,信息对象将在 UpsertedId 字段中包含 ObjectId,但在每个后续 upsert nil 选择器集合将具有记录,并且 upsert 的 nil 选择器将匹配,因此它不会返回 UpsertedId 并且您将更新第一个匹配的记录。

您可以对 upsert 使用从不匹配选择器(这是我的解决方案),但请注意,这样您只会插入和获取插入的 ObjectId,而不会更新记录。

您可以检查以下测试:

func TestFoo(t *testing.T) {
    foo := struct {
        Bar string `bson:"bar"`
    }{
        Bar: "barrr",
    }

    session, _ := mgo.DialWithInfo(&mgo.DialInfo{
        Addrs: []string{"127.0.0.1:27017"},
    })

    coll := session.DB("foo").C("bar")
    coll.DropCollection()

    info, _ := coll.Upsert(nil, foo) // will insert
    count, _ := coll.Count()
    fmt.Printf("%+v, collecton records:%v\n", info, count) // &{Updated:0 Removed:0 Matched:0 UpsertedId:ObjectIdHex("5c35e8ee1f5b80c932b44afb")}, collection records:1

    info, _ = coll.Upsert(bson.M{}, foo) // will update
    count, _ = coll.Count()
    fmt.Printf("%+v, collecton records:%v\n", info, count) // &{Updated:1 Removed:0 Matched:1 UpsertedId:<nil>}, collection records:1

    info, _ = coll.Upsert(bson.M{"nonsense": -1}, foo) // will insert duplicate record (due to the selector that will never match anything)
    count, _ = coll.Count()
    fmt.Printf("%+v, collecton records:%v\n", info, count) // &{Updated:0 Removed:0 Matched:0 UpsertedId:ObjectIdHex("5c35ea2a1f5b80c932b44b1d")}, collection records:2

    foo.Bar = "baz"
    info, _ = coll.Upsert(nil, foo) // will update the first matched (since the selector matches everything)
    count, _ = coll.Count()
    fmt.Printf("%+v, collecton records:%v\n", info, count)

    // after the test the collection will have the following records
    //> use foo
    //> db.bar.find()
    //{ "_id" : ObjectId("5c35ebf41f5b80c932b44b81"), "bar" : "baz" } // the first matched on the last update with nil selector
    //{ "_id" : ObjectId("5c35ebf41f5b80c932b44b86"), "bar" : "barrr" } // the duplicated record with the selector that never matches anything

}

编辑:请注意,您应该在从不匹配的字段上有一个索引,否则如果您有很多记录,您的插入查询将花费太长时间,因为未索引字段上的 upsert 将扫描所有集合中的文档。

【讨论】:

    猜你喜欢
    • 2012-08-14
    • 1970-01-01
    • 2016-01-07
    • 2018-06-15
    • 2018-07-28
    • 2017-04-07
    • 2015-09-17
    • 2017-02-10
    • 1970-01-01
    相关资源
    最近更新 更多