- 插入內(nèi)嵌的document
和關(guān)系型數(shù)據(jù)庫最大的不同,document數(shù)據(jù)庫(非關(guān)系型數(shù)據(jù)庫)不支持模型化的對(duì)象的關(guān)系訪問.在關(guān)系型數(shù)據(jù)庫中,創(chuàng)建一種聯(lián)系,可以使用父表(parent table)和子表,這樣父表的每一個(gè)記錄都會(huì)和子表的多個(gè)記錄相互關(guān)聯(lián).
為了這種數(shù)據(jù)上的對(duì)應(yīng),需要在子表定義額外的key值指向父表的一個(gè)primary key.
MongoDB有著更加靈活的結(jié)構(gòu).所以數(shù)據(jù)模型可以用不同的方式定義獲取同樣的對(duì)象.你可以根據(jù)應(yīng)用的上下文選擇正確的模式定義數(shù)據(jù)模型.為了在鏈接的數(shù)據(jù)建立關(guān)系,可以在主要的document內(nèi)嵌document,或者帶兩個(gè)document之間引用.這兩者的應(yīng)用情形根據(jù)實(shí)際情況去應(yīng)用.
下面的例子展示了一個(gè)數(shù)據(jù)模型使用內(nèi)嵌的document去描述鏈接的數(shù)據(jù)的關(guān)系.Category和Task數(shù)據(jù)的聯(lián)系
Category有多個(gè)Task實(shí)體.
// mongodb
package main
import (
"log"
"time"
"gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson"
)
type Task struct {
Description string
Due time.Time
}
type Category struct {
Id bson.ObjectId `bson:"_id,omitempty"`
Name string
Description string
Tasks []Task
}
func main() {
session, err := mgo.Dial("localhost")
if err != nil {
panic(err)
}
defer session.Close()
session.SetMode(mgo.Monotonic, true)
//獲取一個(gè)集合
c := session.DB("taskdb").C("categories")
//
doc := Category{
bson.NewObjectId(),
"Open-Source",
"Task for open-source prijects",
[]Task{
Task{"Create project in mgo", time.Date(2016, time.May, 10, 0, 0, 0, 0, time.UTC)},
Task{"Create REST API", time.Date(2016, time.May, 20, 0, 0, 0, 0, time.UTC)},
},
}
err = c.Insert(&doc)
if err != nil {
log.Fatal(err)
}
}
創(chuàng)建了一個(gè)Category 結(jié)構(gòu)體,包含了一個(gè)Task元素類型的數(shù)組.文檔的嵌入可以獲取父文檔和關(guān)聯(lián)子文檔,只需要進(jìn)行一次查詢.
- 讀取文檔
Collection的Find方法允許查詢MongoDB的collections.當(dāng)調(diào)用這個(gè)方法的時(shí)候,可以提供一個(gè)文檔進(jìn)行過濾collection數(shù)據(jù). Find方法使用一個(gè)document進(jìn)行查詢.提供一個(gè)document查詢collection,需要提供一個(gè)可以序列化為BSON數(shù)據(jù)的對(duì)象,比如map,struct值.
- 檢索所有記錄
當(dāng)Find方法的參數(shù)為nil時(shí),就會(huì)檢索collection的所有document.下面的是一個(gè)例子:檢索上面的例子保存的所有document
iter := c.Find(nil).Iter()
result := Category{}
for iter.Next(&result) {
fmt.Printf("Category:%s,decription:%Ss\n", result.Name, result.Description)
tasks := result.Tasks
for _, v := range tasks {
fmt.Printf("Task:%s Due:%s\n", v.Description, v.Due)
}
}
if err = iter.Close(); err != nil {
log.Fatal(err)
}
Iter方法用來枚舉documents.Iter執(zhí)行查詢,得到所有的可以枚舉的值.當(dāng)在document中內(nèi)嵌了父子關(guān)系,可以用一個(gè)查詢語句訪問.
- 排序記錄
Documents可以使用Sort方法進(jìn)行排序.Sort方法會(huì)根據(jù)提供的字段來進(jìn)行排序.
//sort
iter := c.Find(nil).Sort("name").Iter()
for iter.Next(&result) {
fmt.Printf("Category:%s,decription:%Ss\n", result.Name, result.Description)
tasks := result.Tasks
for _, v := range tasks {
fmt.Printf("Task:%s Due:%s\n", v.Description, v.Due)
}
}
if err = iter.Close(); err != nil {
log.Fatal(err)
}
如果要根據(jù)字段進(jìn)行反向排序,只要在字段名前加上"-"
iter := c.Find(nil).Sort("-name").Iter()
- 檢索單個(gè)記錄
result := Category{}
err := c.Find(bson.M{"name":"Open-Source"}).One(result)
if err != nil{
log.Fatal(err)
}
fmt.Printf("Category:%s,Description:%s\n",result.name,result.Description)
task := result.Tasks
for _,v := range tasks{
fmt.Printf("Task:%s Due:%v\n",v.Description,v.Due)
}
bson.M (M-->map)類型用來查詢數(shù)據(jù).在這里,使用name字段查詢collection. One方法執(zhí)行查詢并且進(jìn)行解析到result中.還有一個(gè)FindId方法更加方便單個(gè)數(shù)據(jù)的查詢.直接使用id查詢collection中對(duì)應(yīng)的document
query := c.Find(bson.M{"_id":id})
result := Category{}
err = c.FindId(obj_id).One(&result)
- 更新查詢操作
Update方法可以對(duì)document的數(shù)據(jù)進(jìn)行更新.
func (c *Collection) Update(selectorinterface{},updateinterface{}) error
Update方法從collection中查找document,使用提供的選擇器進(jìn)行查找,再用提供的document進(jìn)行進(jìn)行更新.部分更新可以使用"%set"關(guān)鍵字進(jìn)行更新document.
//update a document
err := c.Update(bson.M{"_id": id},
bson.M{"$set":bson.M{
"description":"Create open-source projects",
"tasks":[]Task{
Task{"Evaluate Negroni Project", time.Date(2015, time.August, 15, 0, 0, 0,
0, time.UTC)},
Task{"Explore mgo Project", time.Date(2015, time.August, 10, 0, 0, 0, 0,
time.UTC)},
Task{"Explore Gorilla Toolkit", time.Date(2015, time.August, 10, 0, 0, 0, 0,
time.UTC)},
},
}}
)
部分更新:description和tasks.Update方法會(huì)根據(jù)提供的id進(jìn)行查找,之后修改對(duì)應(yīng)的字段,寫入提供的document的對(duì)應(yīng)的值.
- 刪除一個(gè)document
Remove方法可以從collection中刪除一個(gè)document.
RemoveAll方法則是刪除全部的document,如果參數(shù)為nil,全部刪除
c.RemoveAll(nil)
func (c *Collection) Remove(selector interface{}) error //err := c.Remove(bson.M{"_id": id})
func (c *Collection) RemoveAll(selector interface{}) (info *ChangeInfo, err error)
MongoDB的下標(biāo)索引
MongoDB數(shù)據(jù)庫和關(guān)系型數(shù)據(jù)庫有著高效的讀取操作,為了更好的操作MongoDB數(shù)據(jù)庫,還可以給collection通過添加索引提供效率.collection的索引可以在進(jìn)行高效查詢操作.MongoDB可以在collection水平上定義索引,也可以在collection張document中或者任意字段定義索引.
所有的collection都默認(rèn)有一個(gè)_id字段的所以 .如果不定義這個(gè)字段,MongoDB進(jìn)程(mongod)會(huì)自動(dòng)創(chuàng)建一個(gè)_id字段,值類型是ObjectId. _id索引是唯一的.
如果頻繁的使用某種過濾行為查詢collections,這時(shí)應(yīng)該考慮使用索引,以便更好的操作.mgo數(shù)據(jù)庫驅(qū)動(dòng)提供了EnsureIndex方法,創(chuàng)建索引,參數(shù)是mgo.Index類型.
例子:
// mongodb
package main
import (
"fmt"
"log"
"time"
"gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson"
)
type Task struct {
Description string
Due time.Time
}
type Category struct {
Id bson.ObjectId `bson:"_id,omitempty"`
Name string
Description string
//Tasks []Task
}
func main() {
session, err := mgo.Dial("localhost")
if err != nil {
panic(err)
}
defer session.Close()
session.SetMode(mgo.Monotonic, true)
//獲取一個(gè)集合
c := session.DB("taskdb").C("categories")
c.RemoveAll(nil)
//index
index := mgo.Index{
Key: []string{"name"},
Unique: true,
DropDups: true,
Background: true,
Sparse: true,
}
//create Index
err = c.EnsureIndex(index)
if err != nil {
panic(err)
}
//插入三個(gè)值
err = c.Insert(&Category{bson.NewObjectId(), "R & D", "R & D Tasks"},
&Category{bson.NewObjectId(), "Project", "Project Tasks"},
&Category{bson.NewObjectId(), "Open Source", "Tasks for open-source projects"})
if err != nil {
panic(err)
}
result := Category{}
err = c.Find(bson.M{"name": "Open-Source"}).One(&result)
if err != nil {
log.Fatal(err)
} else {
fmt.Println("Description:", result.Description)
}
創(chuàng)建了一個(gè)mgo.Index,調(diào)用了方法EnsureIndex方法.Index類型的Key屬性,可以用一個(gè)切片作為字段名.在這里name字段作為一個(gè)index.由于字段是一個(gè)切片,可以提供多個(gè)字段給實(shí)例Index.Unique屬性確保只有一個(gè)document有一個(gè)唯一的index.默認(rèn)的index是升序的.如果需要降序,可以在字段前加"-"
key : []string{"-name"}
- 管理session
Dial方法和MongoDB數(shù)據(jù)庫建立了鏈接后會(huì)返回一個(gè)mgo.Session對(duì)象,可以使用這個(gè)對(duì)象管理所有的CRUD操作,
session管理了MongoDB服務(wù)器群的鏈接池. 一個(gè)鏈接池是數(shù)據(jù)庫鏈接的緩存,所以當(dāng)新的請(qǐng)求鏈接數(shù)據(jù)庫的操作是會(huì)重用已用的鏈接.所以當(dāng)開發(fā)web應(yīng)用,如果使用單個(gè)的全局的Session對(duì)錯(cuò)來進(jìn)行全部的CRUD操作是非常糟糕的.
一個(gè)推薦的管理session對(duì)象的流程:
1.使用Dial方法獲取一個(gè)Session對(duì)象.
2.在一個(gè)獨(dú)立的HTTP請(qǐng)求的生命周期,使用New,Copy或者Clone方法創(chuàng)建Session,會(huì)獲取Dial方法創(chuàng)建的session.這樣就能正確使用連接池里面的Session對(duì)象.
3.在HTTP請(qǐng)求的生命周期里面,使用獲取到的Session對(duì)象進(jìn)行CRUD操作.
New方法會(huì)創(chuàng)建一個(gè)新的Session對(duì)象,有同樣的參數(shù).Copy方法和New方法工作方式類似,但是copy會(huì)保留Session原有的信息.Clone方法和Copy一樣,但是會(huì)從用原來的Sesssion的socket.
下面的例子是一個(gè)HTTP服務(wù)器使用一個(gè)copy的Session對(duì)象.一個(gè)struct類型持有Session對(duì)象,在請(qǐng)求的Handler里面非常的輕松管理數(shù)據(jù)庫操作.
// mongodb
package main
import (
"encoding/json"
"log"
"net/http"
"github.com/gorilla/mux"
"gopkg.in/mgo.v2"
"gopkg.in/mgo.v2/bson"
)
var session *mgo.Session
type Category struct {
Id bson.ObjectId `bson:"_id,omitempty"`
Name string
Description string
//Tasks []Task
}
type DataStore struct {
session *mgo.Session
}
/*
type Task struct {
Description string
Due time.Time
}
*/
//獲取數(shù)據(jù)庫的collection
func (d *DataStore) C(name string) *mgo.Collection {
return d.session.DB("taskdb").C(name)
}
//為每一HTTP請(qǐng)求創(chuàng)建新的DataStore對(duì)象
func NewDataStore() *DataStore {
ds := &DataStore{
session: session.Copy(),
}
return ds
}
func (d *DataStore) Close() {
d.session.Close()
}
//插入一個(gè)記錄
func PostCategory(w http.ResponseWriter, r *http.Request) {
var category Category
err := json.NewDecoder(r.Body).Decode(&category)
if err != nil {
panic(err)
}
ds := NewDataStore()
defer ds.Close()
c := ds.C("categories")
err = c.Insert(&category)
if err != nil {
panic(err)
}
w.WriteHeader(http.StatusCreated)
}
func GetCategories(w http.ResponseWriter, r *http.Request) {
var categories []Category
ds := NewDataStore()
defer ds.Close()
c := ds.C("categories")
iter := c.Find(nil).Iter()
result := Category{}
for iter.Next(&result) {
categories = append(categories, result)
}
w.Header().Set("Content-Type", "application/json")
j, err := json.Marshal(categories)
if err != nil {
panic(err)
}
w.WriteHeader(http.StatusOK)
w.Write(j)
}
func main() {
var err error
session, err = mgo.Dial("localhost")
if err != nil {
panic(err)
}
r := mux.NewRouter()
r.HandleFunc("/api/categories", GetCategories).Methods("GET")
r.HandleFunc("/api/categories", PostCategory).Methods("POST")
server := &http.Server{
Addr: ":9090",
Handler: r,
}
log.Println("Listening...")
server.ListenAndServe()
}
定義了一個(gè)DataStore的結(jié)構(gòu)體,管理mgo.Session,還有兩個(gè)方法:Close和C , Close方法主要是Session對(duì)象調(diào)用Close方法,進(jìn)行資源的釋放.defer函數(shù)表示,在HTTP請(qǐng)求生命周期結(jié)束的時(shí)候會(huì)調(diào)用.
NewDataStore方法會(huì)創(chuàng)建一個(gè)新的DataStore對(duì)象,通過Copy函數(shù)獲取Dial函數(shù)的Session對(duì)象.
對(duì)于每個(gè)路由的handler,一個(gè)新的Session對(duì)象都是通過DataStore類型進(jìn)行使用.簡單的說,使用全局的Session對(duì)象的方式不好,推薦在HTTP請(qǐng)求的聲明周期里使用Copy一個(gè)Session對(duì)象的方式.這種方法就會(huì)存在多個(gè)Session對(duì)象.