如何使用mgo插入文档并获取返回的值

时间:2018-02-14 18:39:05

标签: mongodb go mgo

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

编辑:这是模型的结构:

<li class="parsley-required" role="alert"> This value is required.</li>

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

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)

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

2 个答案:

答案 0 :(得分:4)

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

因此,为您的模型类型添加_id文档标识符的字段是理想的(或者强烈建议)。由于我们在这里讨论的是书籍,因此书籍已经有一个名为ISBN的唯一标识符,您可以选择使用该标识符作为_id字段的值。

必须使用bson标记(而不是json)指定MongoDB字段和Go结构字段之间的映射。因此,您应该提供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。此ID生成甚至可以在分布式环境中工作,因此即使您的两个节点尝试同时生成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

答案 1 :(得分:0)

为此,我的头有些僵硬。

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

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

您可以使用永不匹配的选择器进行更新(这是我的解决方案),但是请注意,这样一来,您只会插入并获取插入的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

}

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