我爱编程

Go With MongoDB 2

2016-08-04  本文已影响893人  小Q逛逛

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值.

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的对应的值.

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对象的流程:
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对象.

上一篇下一篇

猜你喜欢

热点阅读