如何使用 mgo 插入文档并获取返回值
How to insert a document with mgo and get the value returned
郑重声明,我正在学习围棋。我正在尝试使用 mgo 包,我想向用户插入一个新文档和 return 这个新创建的文档(我正在尝试编写一个基本的 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 是我之前创建的结构。上面的代码确实有效,但它 returns 是像这样的 upsert 结果:
{
"Updated": 1,
"Removed": 0,
"Matched": 1,
"UpsertedId": null
}
这不是最近创建的对象。如何将最近创建的对象作为响应获取到 return(请注意,理想情况下我希望确认文档已成功插入。我已经看到其他问题,其中 ID 是预先生成的,但是为了什么我看到它没有确认文档已创建,是吗?)
我们先理清概念。在 MongoDB 中,每个文档必须有一个 _id
属性 作为其在集合中的唯一文档标识符。要么您提供此 _id
的值,要么由 MongoDB.
自动分配
因此,您的模型类型最好(或强烈建议)为 _id
文档标识符包含一个字段。由于我们在这里讨论的是书籍,书籍已经有一个称为 ISBN 的唯一标识符,您可以选择将其用作 _id
字段的值。
MongoDB 字段和 Go struct 字段之间的映射必须使用 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
,这意味着任何文档都可能符合条件,因此将选择一个 "randomly"。因此,如果您已经保存了书籍,则任何书籍都可能会被选中并被覆盖。这肯定不是你想要的。
既然现在 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()
. This is similar to Collection.Upsert()
, but the difference is that if no documents match the selector, an insert will not be performed. Updating a document matched by ID can also be done with the more convenient Collection.UpdateId()
(类似于 Collection.UpsertId()
)。
对于自然没有唯一标识符的其他文档(如具有 ISBN 的书籍),您可以使用生成的 ID。 mgo
库提供了 bson.NewObjectId()
function for such purpose, which returns you a value of type bson.ObjectId
。使用 Collection.Insert()
保存新文档时,您可以使用 bson.NewObjectId()
获取一个新的唯一 ID,并将其分配给映射到 MongoDB _id
[=78= 的结构字段].如果插入成功,则可以确定文档的 ID 是您在调用 Collection.Insert()
之前设置的 ID。此 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
为此我的脑袋撞了一会儿。
info, err := c.Upsert(nil, book)
upsert 的 nil
选择器将匹配所有内容,因此如果您的集合为空,选择器将不匹配,一切都会好起来的,信息对象将在 UpsertedId 字段中包含 ObjectId,但是每个后续的带有 nil 选择器的 upsert 集合都会有记录,并且 upsert 的 nil 选择器将匹配,因此它不会 return 你是 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
}
编辑: 注意你应该在永不匹配的字段上有一个索引,否则如果你有很多记录,你的插入查询将花费太长时间,因为更新插入没有索引filed 将扫描集合中的所有文档。
郑重声明,我正在学习围棋。我正在尝试使用 mgo 包,我想向用户插入一个新文档和 return 这个新创建的文档(我正在尝试编写一个基本的 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 是我之前创建的结构。上面的代码确实有效,但它 returns 是像这样的 upsert 结果:
{
"Updated": 1,
"Removed": 0,
"Matched": 1,
"UpsertedId": null
}
这不是最近创建的对象。如何将最近创建的对象作为响应获取到 return(请注意,理想情况下我希望确认文档已成功插入。我已经看到其他问题,其中 ID 是预先生成的,但是为了什么我看到它没有确认文档已创建,是吗?)
我们先理清概念。在 MongoDB 中,每个文档必须有一个 _id
属性 作为其在集合中的唯一文档标识符。要么您提供此 _id
的值,要么由 MongoDB.
因此,您的模型类型最好(或强烈建议)为 _id
文档标识符包含一个字段。由于我们在这里讨论的是书籍,书籍已经有一个称为 ISBN 的唯一标识符,您可以选择将其用作 _id
字段的值。
MongoDB 字段和 Go struct 字段之间的映射必须使用 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
,这意味着任何文档都可能符合条件,因此将选择一个 "randomly"。因此,如果您已经保存了书籍,则任何书籍都可能会被选中并被覆盖。这肯定不是你想要的。
既然现在 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()
. This is similar to Collection.Upsert()
, but the difference is that if no documents match the selector, an insert will not be performed. Updating a document matched by ID can also be done with the more convenient Collection.UpdateId()
(类似于 Collection.UpsertId()
)。
对于自然没有唯一标识符的其他文档(如具有 ISBN 的书籍),您可以使用生成的 ID。 mgo
库提供了 bson.NewObjectId()
function for such purpose, which returns you a value of type bson.ObjectId
。使用 Collection.Insert()
保存新文档时,您可以使用 bson.NewObjectId()
获取一个新的唯一 ID,并将其分配给映射到 MongoDB _id
[=78= 的结构字段].如果插入成功,则可以确定文档的 ID 是您在调用 Collection.Insert()
之前设置的 ID。此 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
为此我的脑袋撞了一会儿。
info, err := c.Upsert(nil, book)
upsert 的 nil
选择器将匹配所有内容,因此如果您的集合为空,选择器将不匹配,一切都会好起来的,信息对象将在 UpsertedId 字段中包含 ObjectId,但是每个后续的带有 nil 选择器的 upsert 集合都会有记录,并且 upsert 的 nil 选择器将匹配,因此它不会 return 你是 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
}
编辑: 注意你应该在永不匹配的字段上有一个索引,否则如果你有很多记录,你的插入查询将花费太长时间,因为更新插入没有索引filed 将扫描集合中的所有文档。