为了记录,我正在学习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是事先生成的,但是我已经看到了它不能确认文件是否已创建?)
答案 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
}
编辑:请注意,您应该在永不匹配的字段上建立索引,否则如果您有很多记录,则插入查询将花费很长时间,因为未索引字段上的更新将扫描所有集合中的文件。