Go With MongoDB 2

作者: 小Q逛逛 | 来源:发表于2016-08-04 14:01 被阅读893次
    • 插入内嵌的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对象.

    相关文章

      网友评论

        本文标题:Go With MongoDB 2

        本文链接:https://www.haomeiwen.com/subject/msbcsttx.html