Go With MongoDB 2

  • 插入内嵌的document
    和关系型数据库最大的不同,document数据库(非关系型数据库)不支持模型化的对象的关系访问.在关系型数据库中,创建一种联系,可以使用父表(parent table)和子表,这样父表的每一个记录都会和子表的多个记录相互关联.
    为了这种数据上的对应,需要在子表定义额外的key值指向父表的一个primary key.

MongoDB有着更加灵活的结构.所以数据模型可以用不同的方式定义获取同样的对象.你可以根据应用的上下文选择正确的模式定义数据模型.为了在链接的数据建立关系,可以在主要的document内嵌document,或者带两个document之间引用.这两者的应用情形根据实际情况去应用.

下面的例子展示了一个数据模型使用内嵌的document去描述链接的数据的关系.Category和Task数据的联系
Category有多个Task实体.

// 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)
    //获取一个集合
    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)
    }

}

创建了一个Category 结构体,包含了一个Task元素类型的数组.文档的嵌入可以获取父文档和关联子文档,只需要进行一次查询.

  • 读取文档

Collection的Find方法允许查询MongoDB的collections.当调用这个方法的时候,可以提供一个文档进行过滤collection数据. Find方法使用一个document进行查询.提供一个document查询collection,需要提供一个可以序列化为BSON数据的对象,比如map,struct值.

  • 检索所有记录
    当Find方法的参数为nil时,就会检索collection的所有document.下面的是一个例子:检索上面的例子保存的所有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执行查询,得到所有的可以枚举的值.当在document中内嵌了父子关系,可以用一个查询语句访问.

  • 排序记录

Documents可以使用Sort方法进行排序.Sort方法会根据提供的字段来进行排序.

    //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)
    }

如果要根据字段进行反向排序,只要在字段名前加上"-"

    iter := c.Find(nil).Sort("-name").Iter()

  • 检索单个记录
 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)类型用来查询数据.在这里,使用name字段查询collection. One方法执行查询并且进行解析到result中.还有一个FindId方法更加方便单个数据的查询.直接使用id查询collection中对应的document

query := c.Find(bson.M{"_id":id})

result := Category{}
err = c.FindId(obj_id).One(&result)

  • 更新查询操作

Update方法可以对document的数据进行更新.

func (c *Collection) Update(selectorinterface{},updateinterface{}) error

Update方法从collection中查找document,使用提供的选择器进行查找,再用提供的document进行进行更新.部分更新可以使用"%set"关键字进行更新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方法会根据提供的id进行查找,之后修改对应的字段,写入提供的document的对应的值.

  • 删除一个document

Remove方法可以从collection中删除一个document.
RemoveAll方法则是删除全部的document,如果参数为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的下标索引

MongoDB数据库和关系型数据库有着高效的读取操作,为了更好的操作MongoDB数据库,还可以给collection通过添加索引提供效率.collection的索引可以在进行高效查询操作.MongoDB可以在collection水平上定义索引,也可以在collection张document中或者任意字段定义索引.

所有的collection都默认有一个_id字段的所以 .如果不定义这个字段,MongoDB进程(mongod)会自动创建一个_id字段,值类型是ObjectId. _id索引是唯一的.

如果频繁的使用某种过滤行为查询collections,这时应该考虑使用索引,以便更好的操作.mgo数据库驱动提供了EnsureIndex方法,创建索引,参数是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)
    //获取一个集合
    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)
    }

    //插入三个值
    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)

    }

创建了一个mgo.Index,调用了方法EnsureIndex方法.Index类型的Key属性,可以用一个切片作为字段名.在这里name字段作为一个index.由于字段是一个切片,可以提供多个字段给实例Index.Unique属性确保只有一个document有一个唯一的index.默认的index是升序的.如果需要降序,可以在字段前加"-"

key : []string{"-name"}

  • 管理session
    Dial方法和MongoDB数据库建立了链接后会返回一个mgo.Session对象,可以使用这个对象管理所有的CRUD操作,
    session管理了MongoDB服务器群的链接池. 一个链接池是数据库链接的缓存,所以当新的请求链接数据库的操作是会重用已用的链接.所以当开发web应用,如果使用单个的全局的Session对错来进行全部的CRUD操作是非常糟糕的.

一个推荐的管理session对象的流程:
1.使用Dial方法获取一个Session对象.
2.在一个独立的HTTP请求的生命周期,使用New,Copy或者Clone方法创建Session,会获取Dial方法创建的session.这样就能正确使用连接池里面的Session对象.
3.在HTTP请求的生命周期里面,使用获取到的Session对象进行CRUD操作.

New方法会创建一个新的Session对象,有同样的参数.Copy方法和New方法工作方式类似,但是copy会保留Session原有的信息.Clone方法和Copy一样,但是会从用原来的Sesssion的socket.

下面的例子是一个HTTP服务器使用一个copy的Session对象.一个struct类型持有Session对象,在请求的Handler里面非常的轻松管理数据库操作.

// 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
}
*/

//获取数据库的collection
func (d *DataStore) C(name string) *mgo.Collection {
    return d.session.DB("taskdb").C(name)
}

//为每一HTTP请求创建新的DataStore对象
func NewDataStore() *DataStore {
    ds := &DataStore{
        session: session.Copy(),
    }
    return ds
}

func (d *DataStore) Close() {
    d.session.Close()
}

//插入一个记录
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()
}

定义了一个DataStore的结构体,管理mgo.Session,还有两个方法:Close和C , Close方法主要是Session对象调用Close方法,进行资源的释放.defer函数表示,在HTTP请求生命周期结束的时候会调用.

NewDataStore方法会创建一个新的DataStore对象,通过Copy函数获取Dial函数的Session对象.
对于每个路由的handler,一个新的Session对象都是通过DataStore类型进行使用.简单的说,使用全局的Session对象的方式不好,推荐在HTTP请求的声明周期里使用Copy一个Session对象的方式.这种方法就会存在多个Session对象.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容