Go With MongoDB 2

  • 插入內(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ì)象.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末噪服,一起剝皮案震驚了整個(gè)濱河市译仗,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌缝裤,老刑警劉巖件缸,帶你破解...
    沈念sama閱讀 211,042評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件铜靶,死亡現(xiàn)場離奇詭異,居然都是意外死亡停团,警方通過查閱死者的電腦和手機(jī)旷坦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來佑稠,“玉大人秒梅,你說我怎么就攤上這事∩嘟海” “怎么了捆蜀?”我有些...
    開封第一講書人閱讀 156,674評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長幔嫂。 經(jīng)常有香客問我辆它,道長,這世上最難降的妖魔是什么履恩? 我笑而不...
    開封第一講書人閱讀 56,340評(píng)論 1 283
  • 正文 為了忘掉前任锰茉,我火速辦了婚禮,結(jié)果婚禮上切心,老公的妹妹穿的比我還像新娘飒筑。我一直安慰自己,他們只是感情好绽昏,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評(píng)論 5 384
  • 文/花漫 我一把揭開白布协屡。 她就那樣靜靜地躺著,像睡著了一般全谤。 火紅的嫁衣襯著肌膚如雪肤晓。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,749評(píng)論 1 289
  • 那天认然,我揣著相機(jī)與錄音补憾,去河邊找鬼。 笑死卷员,一個(gè)胖子當(dāng)著我的面吹牛余蟹,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播子刮,決...
    沈念sama閱讀 38,902評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了挺峡?” 一聲冷哼從身側(cè)響起葵孤,我...
    開封第一講書人閱讀 37,662評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎橱赠,沒想到半個(gè)月后尤仍,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,110評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡狭姨,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年宰啦,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片饼拍。...
    茶點(diǎn)故事閱讀 38,577評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡赡模,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出师抄,到底是詐尸還是另有隱情漓柑,我是刑警寧澤,帶...
    沈念sama閱讀 34,258評(píng)論 4 328
  • 正文 年R本政府宣布叨吮,位于F島的核電站辆布,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏茶鉴。R本人自食惡果不足惜锋玲,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望涵叮。 院中可真熱鬧惭蹂,春花似錦、人聲如沸围肥。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽穆刻。三九已至置尔,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間氢伟,已是汗流浹背榜轿。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評(píng)論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留朵锣,地道東北人谬盐。 一個(gè)月前我還...
    沈念sama閱讀 46,271評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像诚些,于是被迫代替她去往敵國和親飞傀。 傳聞我的和親對(duì)象是個(gè)殘疾皇子皇型,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容